lnd/simulation/command-center/data/run.json
2026-07-25 01:06:11 -07:00

536 lines
No EOL
739 KiB
JSON

{
"run_id": "code_split2",
"reflection_lm": "codex:gpt-5.6-sol",
"mode": "generalization",
"status": "complete",
"seed_score": 0.6293,
"best_score": 0.9668,
"iterations": [
{
"i": 0,
"candidate_score": 0.6293,
"best_score": 0.6293,
"note": "seed"
},
{
"i": 1,
"candidate_score": 0.7726,
"best_score": 0.7726,
"note": "accepted"
},
{
"i": 2,
"candidate_score": 0.7835,
"best_score": 0.7726,
"note": "rejected"
},
{
"i": 3,
"candidate_score": 0.5285,
"best_score": 0.7726,
"note": "accepted"
},
{
"i": 4,
"candidate_score": -0.15,
"best_score": 0.7726,
"note": "rejected"
},
{
"i": 5,
"candidate_score": 0.0,
"best_score": 0.7726,
"note": "rejected"
},
{
"i": 6,
"candidate_score": 0.7947,
"best_score": 0.7947,
"note": "accepted"
},
{
"i": 7,
"candidate_score": 0.5021,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 8,
"candidate_score": 0.5085,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 9,
"candidate_score": -0.1122,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 10,
"candidate_score": 0.0,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 11,
"candidate_score": 0.6389,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 12,
"candidate_score": 0.0,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 13,
"candidate_score": 0.9088,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 14,
"candidate_score": 0.9062,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 15,
"candidate_score": 0.7381,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 16,
"candidate_score": 0.5078,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 17,
"candidate_score": 0.7688,
"best_score": 0.7947,
"note": "accepted"
},
{
"i": 18,
"candidate_score": 0.7666,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 19,
"candidate_score": 0.7764,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 20,
"candidate_score": 0.7682,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 21,
"candidate_score": 0.5077,
"best_score": 0.7947,
"note": "accepted"
},
{
"i": 22,
"candidate_score": 0.7677,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 23,
"candidate_score": 0.0,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 24,
"candidate_score": 0.6134,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 25,
"candidate_score": 0.7755,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 26,
"candidate_score": 0.0,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 27,
"candidate_score": 0.6497,
"best_score": 0.7947,
"note": "rejected"
},
{
"i": 28,
"candidate_score": 0.8647,
"best_score": 0.8647,
"note": "accepted"
},
{
"i": 29,
"candidate_score": 0.7261,
"best_score": 0.8647,
"note": "accepted"
},
{
"i": 30,
"candidate_score": 0.6421,
"best_score": 0.8647,
"note": "accepted"
},
{
"i": 31,
"candidate_score": 0.0,
"best_score": 0.8647,
"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 (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.68\n\tmaxShardChoices = 96\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n\tfailureCeiling lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 250_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 50_000_000:\n\t\tparts = 2\n\t}\n\tif parts > spec.MaxParts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampAmount(amt, capacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amt < 0 {\n\t\treturn 0\n\t}\n\tif amt > capacity {\n\t\treturn capacity\n\t}\n\treturn amt\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) availableLocal(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\treserved := r.reserved[key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.availableLocal(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tweight := 0.48\n\tif belief.lowerOK != 0 || belief.upperBad != 0 {\n\t\tweight = 0.64\n\t}\n\n\treturn clampProbability(\n\t\t(1-weight)*prior + weight*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.availableLocal(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\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\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\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\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendShardChoice(choices *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, candidate, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif candidate < minimum || candidate > maximum ||\n\t\tcandidate <= 0 || seen[candidate] ||\n\t\tlen(*choices) >= maxShardChoices {\n\n\t\treturn\n\t}\n\n\tseen[candidate] = true\n\t*choices = append(*choices, candidate)\n}\n\nfunc (r *candidateRouter) shardChoices(amt,\n\tminNeeded lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tchoices := make([]lnwire.MilliSatoshi, 0, 32)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tappendChoice := func(candidate lnwire.MilliSatoshi) {\n\t\tappendShardChoice(\n\t\t\t&choices, seen, candidate, minNeeded, amt,\n\t\t)\n\t}\n\n\tappendChoice(minNeeded)\n\tappendChoice(amt)\n\tappendChoice(r.baseShard)\n\n\tfor parts := uint32(1); parts <= partsLeft; parts++ {\n\t\tappendChoice(ceilDiv(\n\t\t\tamt, lnwire.MilliSatoshi(parts),\n\t\t))\n\t}\n\n\tfor _, ratio := range []int64{\n\t\t90, 82, 74, 68, 60, 52, 45, 38,\n\t} {\n\t\tappendChoice(amt * lnwire.MilliSatoshi(ratio) / 100)\n\t}\n\n\tfor _, belief := range r.beliefs {\n\t\tif len(choices) >= maxShardChoices {\n\t\t\tbreak\n\t\t}\n\n\t\tappendChoice(belief.lowerOK)\n\t\tappendChoice(belief.lowerOK * 9 / 10)\n\t\tappendChoice(belief.estimate * 4 / 5)\n\t\tif belief.upperBad != 0 {\n\t\t\tappendChoice(belief.upperBad * 2 / 3)\n\t\t\tif belief.upperBad > 1 {\n\t\t\t\tappendChoice(belief.upperBad - 1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.failureCeiling != 0 {\n\t\tfiltered := choices[:0]\n\t\tfor _, choice := range choices {\n\t\t\tif choice <= r.failureCeiling ||\n\t\t\t\tchoice == minNeeded {\n\n\t\t\t\tfiltered = append(filtered, choice)\n\t\t\t}\n\t\t}\n\t\tchoices = filtered\n\t}\n\n\treturn choices\n}\n\nfunc (r *candidateRouter) routeQuality(rt *route.Route,\n\tminNeeded lnwire.MilliSatoshi) float64 {\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered <= 0 {\n\t\treturn math.Inf(1)\n\t}\n\n\tnegativeLogProbability := 0.0\n\tpenalty := 0.0\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn math.Inf(1)\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability := r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t\tnegativeLogProbability -= math.Log(probability)\n\t\tpenalty += r.routePenalty[key] / riskCostMsat\n\t}\n\n\tfee := rt.TotalAmount - delivered\n\tfeeCost := 60000 * float64(fee) / float64(delivered)\n\n\tsizeBenefit := 0.0\n\tif minNeeded > 0 && delivered > minNeeded {\n\t\tsizeBenefit = 0.20 * math.Log(\n\t\t\tfloat64(delivered) / float64(minNeeded),\n\t\t)\n\t}\n\n\thopCost := 0.004 * float64(len(rt.Hops))\n\treturn negativeLogProbability + feeCost + penalty +\n\t\thopCost - sizeBenefit\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.failureCeiling = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tminNeeded := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\n\tchoices := r.shardChoices(amt, minNeeded, partsLeft)\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestQuality = math.Inf(1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range choices {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tquality := r.routeQuality(rt, minNeeded)\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\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edgeByKey[key]\n\tif edge != nil {\n\t\tbelief.lowerOK = clampAmount(\n\t\t\tbelief.lowerOK, edge.capacity,\n\t\t)\n\t\tbelief.estimate = clampAmount(\n\t\t\tbelief.estimate, edge.capacity,\n\t\t)\n\t\tif belief.upperBad > edge.capacity {\n\t\t\tbelief.upperBad = 0\n\t\t}\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt + (edge.capacity-amt)*4/5\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) settleLiquidity(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tbalance := r.localBalances[key.chanID]\n\t\tif amt >= balance {\n\t\t\tr.localBalances[key.chanID] = 0\n\t\t} else {\n\t\t\tr.localBalances[key.chanID] = balance - amt\n\t\t}\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tprovenBefore := belief.lowerOK\n\tif amt > provenBefore {\n\t\tprovenBefore = amt\n\t}\n\n\tif provenBefore > amt {\n\t\tbelief.lowerOK = provenBefore - amt\n\t} else {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tif belief.upperBad > amt {\n\t\tbelief.upperBad -= amt\n\t} else {\n\t\tbelief.upperBad = 0\n\t}\n\n\tpreEstimate := belief.estimate\n\tminimumEstimate := amt + (edge.capacity-amt)*4/5\n\tif preEstimate < minimumEstimate {\n\t\tpreEstimate = minimumEstimate\n\t}\n\tif preEstimate > amt {\n\t\tbelief.estimate = preEstimate - amt\n\t} else {\n\t\tbelief.estimate = 0\n\t}\n\n\tr.saveBelief(key, belief)\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edgeByKey[reverseKey]\n\tif reverseEdge == nil || reverseKey.from == r.source {\n\t\treturn\n\t}\n\n\treverse := r.beliefs[reverseKey]\n\tif reverse.lowerOK < amt {\n\t\treverse.lowerOK = amt\n\t}\n\treverse.estimate = clampAmount(\n\t\treverse.estimate+amt, reverseEdge.capacity,\n\t)\n\tif reverse.estimate < reverse.lowerOK {\n\t\treverse.estimate = reverse.lowerOK\n\t}\n\tif reverse.upperBad != 0 &&\n\t\treverse.upperBad <= reverse.lowerOK {\n\n\t\treverse.upperBad = 0\n\t}\n\n\tr.saveBelief(reverseKey, reverse)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 35 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamt := routeAmount(rt, i)\n\t\t\tr.recordPass(key, amt)\n\t\t\tr.settleLiquidity(key, amt)\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\tr.failureCeiling = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 2\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.75\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmount := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmount)\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 2 {\n\t\tdelivered := deliveredAmount(rt)\n\t\tceiling := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif ceiling > 0 &&\n\t\t\t(r.failureCeiling == 0 ||\n\t\t\t\tceiling < r.failureCeiling) {\n\n\t\t\tr.failureCeiling = ceiling\n\t\t}\n\t\tif ceiling > 0 && ceiling < r.baseShard {\n\t\t\tr.baseShard = ceiling\n\t\t}\n\t}\n\n\treturn nil\n}"
},
"stats": {
"evals_done": 261,
"distinct_candidates": 32
},
"candidates": [
{
"id": 0,
"parent": null,
"score": 0.6293,
"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.7726,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.68\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 250_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 50_000_000:\n\t\tparts = 2\n\t}\n\tif spec.MaxParts < parts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(spec.Amount, lnwire.MilliSatoshi(parts))\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tavailable := r.localBalances[edge.key.chanID]\n\t\tif reserved := r.reserved[edge.key]; reserved < available {\n\t\t\tavailable -= reserved\n\t\t} else {\n\t\t\tavailable = 0\n\t\t}\n\t\tif amt > available {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.08, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.55*prior + 0.45*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tif probability <= minProbability &&\n\t\t\t\tr.beliefs[edge.key].upperBad != 0 {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t} else {\n\t\t\t\tavailable := r.localBalances[edge.key.chanID]\n\t\t\t\tif reserved < available {\n\t\t\t\t\tavailable -= reserved\n\t\t\t\t} else {\n\t\t\t\t\tavailable = 0\n\t\t\t\t}\n\t\t\t\tif sending > available {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\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\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tr.reserved[key] += routeAmount(rt, i)\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tshard := r.baseShard\n\tif shard <= 0 || shard > amt {\n\t\tshard = amt\n\t}\n\n\tminNeeded := ceilDiv(amt, lnwire.MilliSatoshi(partsLeft))\n\tif shard < minNeeded {\n\t\tshard = minNeeded\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tminShard := lnwire.MilliSatoshi(10_000)\n\tif amt < minShard {\n\t\tminShard = amt\n\t}\n\n\tvar lastErr error\n\tfor shard >= minShard {\n\t\trt, err := r.findRoute(shard)\n\t\tif err == nil {\n\t\t\tr.reserve(rt)\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\n\t\tnextShard := lnwire.MilliSatoshi(\n\t\t\tfloat64(shard) * lowerRetryFactor,\n\t\t)\n\t\tif nextShard >= shard {\n\t\t\tnextShard = shard - 1\n\t\t}\n\t\tif nextShard < minNeeded {\n\t\t\tbreak\n\t\t}\n\t\tshard = nextShard\n\t}\n\n\tif shard != minNeeded {\n\t\trt, err := r.findRoute(minNeeded)\n\t\tif err == nil {\n\t\t\tr.reserve(rt)\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tremaining := edge.capacity - amt\n\tif remaining > 0 {\n\t\testimate += remaining * 3 / 4\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 35 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\t\tr.routePenalty[key] *= 0.25\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 2\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tr.recordFailure(key, routeAmount(rt, failIndex))\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 2 {\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif retry > 0 && retry < r.baseShard {\n\t\t\tr.baseShard = retry\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 2,
"parent": 1,
"score": 0.7835,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.70\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 250_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 50_000_000:\n\t\tparts = 2\n\t}\n\tif spec.MaxParts != 0 && parts > spec.MaxParts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampAmount(amt, capacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amt < 0 {\n\t\treturn 0\n\t}\n\tif amt > capacity {\n\t\treturn capacity\n\t}\n\treturn amt\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[edge.key.chanID]\n\n\tspent := r.localSpent[edge.key.chanID]\n\tif spent >= available {\n\t\treturn 0\n\t}\n\tavailable -= spent\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.50*prior + 0.50*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif required[item.node] != item.arriving {\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.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tif sending > r.localAvailable(edge) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tif fee < 0 ||\n\t\t\t\t\tamtOver > lnwire.MilliSatoshi(\n\t\t\t\t\t\tmath.MaxInt64,\n\t\t\t\t\t)-fee {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(pq, &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})\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\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tfee := forwardingEdge.fee(amtOver[i+1])\n\t\tif fee < 0 ||\n\t\t\tamtOver[i+1] > lnwire.MilliSatoshi(\n\t\t\t\tmath.MaxInt64,\n\t\t\t)-fee {\n\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeProbability(rt *route.Route) float64 {\n\tprobability := 1.0\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability *= r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t}\n\n\tif probability < math.SmallestNonzeroFloat64 {\n\t\treturn math.SmallestNonzeroFloat64\n\t}\n\treturn probability\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendCandidate(values []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn values\n\t}\n\n\tseen[value] = true\n\treturn append(values, value)\n}\n\nfunc (r *candidateRouter) shardCandidates(amt,\n\tminimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 12)\n\n\tfor _, numerator := range []int64{\n\t\t100, 84, 70, 58, 48, 40, 33, 27,\n\t} {\n\t\tvalue := lnwire.MilliSatoshi(\n\t\t\tint64(amt) * numerator / 100,\n\t\t)\n\t\tvalues = appendCandidate(\n\t\t\tvalues, seen, value, minimum, amt,\n\t\t)\n\t}\n\n\tvalues = appendCandidate(\n\t\tvalues, seen, r.baseShard, minimum, amt,\n\t)\n\tvalues = appendCandidate(\n\t\tvalues, seen, minimum, minimum, amt,\n\t)\n\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 ||\n\t\tinFlightHtlcs >= r.spec.MaxParts {\n\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tminimum := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestUtility = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range r.shardCandidates(amt, minimum) {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := r.routeProbability(rt)\n\t\tsizeRatio := float64(shard) / float64(minimum)\n\t\tfee := rt.TotalAmount - deliveredAmount(rt)\n\n\t\tutility := math.Log(probability) +\n\t\t\t1.35*math.Log(sizeRatio) -\n\t\t\tfloat64(fee)/2_000_000\n\n\t\tif shard == amt {\n\t\t\tutility += 0.08\n\t\t}\n\t\tif utility > bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief.lowerOK = clampAmount(\n\t\tbelief.lowerOK, edge.capacity,\n\t)\n\tbelief.upperBad = clampAmount(\n\t\tbelief.upperBad, edge.capacity,\n\t)\n\tbelief.estimate = clampAmount(\n\t\tbelief.estimate, edge.capacity,\n\t)\n\n\tif belief.upperBad != 0 &&\n\t\tbelief.lowerOK >= belief.upperBad {\n\n\t\tbelief.upperBad = 0\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordProbeSuccess(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tif edge.capacity > amt {\n\t\testimate += (edge.capacity - amt) * 3 / 4\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc subtractBound(value,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value == 0 || value <= amt {\n\t\treturn 0\n\t}\n\treturn value - amt\n}\n\nfunc addBound(value, amt,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value > capacity-amt {\n\t\treturn capacity\n\t}\n\treturn value + amt\n}\n\nfunc (r *candidateRouter) recordSettlement(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\tif inferred > preEstimate {\n\t\tpreEstimate = inferred\n\t}\n\n\tbelief.lowerOK = subtractBound(belief.lowerOK, amt)\n\tbelief.upperBad = subtractBound(belief.upperBad, amt)\n\tbelief.estimate = subtractBound(preEstimate, amt)\n\tr.saveBelief(key, belief)\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edgeByKey[reverseKey]\n\tif reverseEdge == nil || reverseKey.from == r.source {\n\t\treturn\n\t}\n\n\treverse := r.beliefs[reverseKey]\n\treverse.lowerOK = addBound(\n\t\treverse.lowerOK, amt, reverseEdge.capacity,\n\t)\n\tif reverse.lowerOK < amt {\n\t\treverse.lowerOK = amt\n\t}\n\treverse.estimate = addBound(\n\t\treverse.estimate, amt, reverseEdge.capacity,\n\t)\n\tif reverse.estimate < reverse.lowerOK {\n\t\treverse.estimate = reverse.lowerOK\n\t}\n\tif reverse.upperBad != 0 {\n\t\treverse.upperBad = addBound(\n\t\t\treverse.upperBad, amt, reverseEdge.capacity,\n\t\t)\n\t}\n\n\tr.saveBelief(reverseKey, reverse)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 2\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordProbeSuccess(\n\t\t\t\tkey, routeAmount(rt, i),\n\t\t\t)\n\t\t\tr.routePenalty[key] *= 0.75\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tr.recordFailure(\n\t\t\tkey, routeAmount(rt, failIndex),\n\t\t)\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tretry := lnwire.MilliSatoshi(\n\t\tfloat64(delivered) * lowerRetryFactor,\n\t)\n\tif retry > 0 && retry < r.baseShard {\n\t\tr.baseShard = retry\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 3,
"parent": 1,
"score": 0.5285,
"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\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 260000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tdiversityCost = 6000000.0\n\tminimumShard = lnwire.MilliSatoshi(10_000)\n\tmaxPlanCorridors = 8\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype pathCandidate struct {\n\tedges []*candidateEdge\n\tlimit lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n\tretryHint lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amount >= value {\n\t\treturn 0\n\t}\n\treturn value - amount\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\tavailable = subtractFloor(available, r.localSpent[key.chanID])\n\tavailable = subtractFloor(available, r.reserved[key])\n\n\treturn available\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.52*prior + 0.48*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]int) ([]*candidateEdge, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 && amtOver >= belief.upperBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\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\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tcapacityBonus := 0.0\n\t\t\tif edge.capacity > 0 {\n\t\t\t\tspare := float64(edge.capacity-amtOver) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tcapacityBonus = math.Min(spare, 1) * 18000\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250 -\n\t\t\t\tcapacityBonus\n\n\t\t\tif count := diversity[edge.key]; count != 0 {\n\t\t\t\tscore += float64(count) * diversityCost\n\t\t\t}\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, 0, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, 0, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 0, errors.New(\"empty route\")\n\t}\n\n\treturn path, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.usable(amtOver[i]) {\n\t\t\treturn nil, errors.New(\"path cannot carry amount\")\n\t\t}\n\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeFeasible(rt *route.Route) bool {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok || r.blocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tamt := routeAmount(rt, i)\n\t\tif edge == nil || !edge.usable(amt) {\n\t\t\treturn false\n\t\t}\n\n\t\tif edge.key.from == r.source {\n\t\t\tif amt > r.localAvailable(key) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\tif reserved >= edge.capacity ||\n\t\t\tamt > edge.capacity-reserved {\n\n\t\t\treturn false\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) pathLimit(path []*candidateEdge,\n\tmaxAmount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif maxAmount <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := maxAmount\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\trt, err := r.buildRoute(mid, path)\n\t\tif err == nil && r.routeFeasible(rt) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc pathID(path []*candidateEdge) string {\n\tid := \"\"\n\tfor _, edge := range path {\n\t\tid += fmt.Sprintf(\n\t\t\t\"%d:%x:%x/\", edge.key.chanID, edge.key.from,\n\t\t\tedge.key.to,\n\t\t)\n\t}\n\treturn id\n}\n\nfunc (r *candidateRouter) probeAmount(remaining lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\n\tprobe := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(parts)*6,\n\t)\n\tif probe < 100_000 {\n\t\tprobe = 100_000\n\t}\n\tif probe > 50_000_000 {\n\t\tprobe = 50_000_000\n\t}\n\tif probe > remaining {\n\t\tprobe = remaining\n\t}\n\n\treturn probe\n}\n\nfunc (r *candidateRouter) planPaths(remaining lnwire.MilliSatoshi,\n\tparts uint32) []pathCandidate {\n\n\tcount := int(parts)\n\tif count < 1 {\n\t\tcount = 1\n\t}\n\tif count > maxPlanCorridors {\n\t\tcount = maxPlanCorridors\n\t}\n\n\tprobes := []lnwire.MilliSatoshi{\n\t\tr.probeAmount(remaining, parts),\n\t}\n\tif probes[0] > minimumShard {\n\t\tprobes = append(probes, minimumShard)\n\t}\n\n\tdiversity := make(map[candidateEdgeKey]int)\n\tseen := make(map[string]bool)\n\tplans := make([]pathCandidate, 0, count)\n\n\tfor len(plans) < count {\n\t\tvar (\n\t\t\tpath []*candidateEdge\n\t\t\tscore float64\n\t\t\terr error\n\t\t)\n\n\t\tfor _, probe := range probes {\n\t\t\tpath, score, err = r.findPath(probe, diversity)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tid := pathID(path)\n\t\tfor _, edge := range path {\n\t\t\tdiversity[edge.key]++\n\t\t}\n\n\t\tif seen[id] {\n\t\t\tif len(plans) != 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tseen[id] = true\n\n\t\tlimit := r.pathLimit(path, remaining)\n\t\tif limit < minimumShard && limit < remaining {\n\t\t\tcontinue\n\t\t}\n\n\t\tplans = append(plans, pathCandidate{\n\t\t\tedges: path,\n\t\t\tlimit: limit,\n\t\t\tscore: score,\n\t\t})\n\t}\n\n\tsort.SliceStable(plans, func(i, j int) bool {\n\t\tpi := r.pathReliability(plans[i])\n\t\tpj := r.pathReliability(plans[j])\n\t\tif math.Abs(pi-pj) > 0.03 {\n\t\t\treturn pi > pj\n\t\t}\n\t\tif plans[i].limit != plans[j].limit {\n\t\t\treturn plans[i].limit > plans[j].limit\n\t\t}\n\t\treturn plans[i].score < plans[j].score\n\t})\n\n\treturn plans\n}\n\nfunc (r *candidateRouter) pathReliability(\n\tplan pathCandidate) float64 {\n\n\tif plan.limit <= 0 {\n\t\treturn 0\n\t}\n\n\tprobe := plan.limit\n\tif probe > 50_000_000 {\n\t\tprobe = 50_000_000\n\t}\n\n\tprobability := 1.0\n\tfor _, edge := range plan.edges {\n\t\tprobability *= r.probability(edge, probe)\n\t}\n\n\treturn probability\n}\n\nfunc plannedAllocation(remaining lnwire.MilliSatoshi,\n\tplans []pathCandidate) lnwire.MilliSatoshi {\n\n\tif len(plans) == 0 {\n\t\treturn 0\n\t}\n\tif len(plans) == 1 {\n\t\tif plans[0].limit < remaining {\n\t\t\treturn plans[0].limit\n\t\t}\n\t\treturn remaining\n\t}\n\n\ttotal := lnwire.MilliSatoshi(0)\n\tother := lnwire.MilliSatoshi(0)\n\tfor i, plan := range plans {\n\t\ttotal += plan.limit\n\t\tif i != 0 {\n\t\t\tother += plan.limit\n\t\t}\n\t}\n\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\tallocation := lnwire.MilliSatoshi(\n\t\tfloat64(remaining) *\n\t\t\tfloat64(plans[0].limit) / float64(total),\n\t)\n\n\trequiredNow := subtractFloor(remaining, other)\n\tif allocation < requiredNow {\n\t\tallocation = requiredNow\n\t}\n\n\tfairMinimum := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(len(plans)),\n\t)\n\tif allocation < fairMinimum &&\n\t\tplans[0].limit >= fairMinimum {\n\n\t\tallocation = fairMinimum\n\t}\n\n\tif allocation > plans[0].limit {\n\t\tallocation = plans[0].limit\n\t}\n\tif allocation > remaining {\n\t\tallocation = remaining\n\t}\n\tif allocation < minimumShard && remaining >= minimumShard {\n\t\tif plans[0].limit >= minimumShard {\n\t\t\tallocation = minimumShard\n\t\t}\n\t}\n\n\treturn allocation\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tr.reserved[key] += routeAmount(rt, i)\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tplans := r.planPaths(amt, partsLeft)\n\tif len(plans) != 0 {\n\t\tshard := plannedAllocation(amt, plans)\n\n\t\tif r.retryHint != 0 && shard > r.retryHint {\n\t\t\tshard = r.retryHint\n\t\t}\n\t\tif shard > plans[0].limit {\n\t\t\tshard = plans[0].limit\n\t\t}\n\t\tif shard > amt {\n\t\t\tshard = amt\n\t\t}\n\n\t\tfor shard > 0 {\n\t\t\trt, err := r.buildRoute(shard, plans[0].edges)\n\t\t\tif err == nil && r.routeFeasible(rt) {\n\t\t\t\tr.reserve(rt)\n\t\t\t\treturn rt, nil\n\t\t\t}\n\n\t\t\tnext := shard * 2 / 3\n\t\t\tif next >= shard {\n\t\t\t\tnext = shard - 1\n\t\t\t}\n\t\t\tif next < minimumShard && amt >= minimumShard {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tshard = next\n\t\t}\n\t}\n\n\tcandidates := []lnwire.MilliSatoshi{\n\t\tamt,\n\t\tceilDiv(amt, lnwire.MilliSatoshi(partsLeft)),\n\t\tamt * 2 / 3,\n\t\tamt / 2,\n\t\tamt / 3,\n\t\tminimumShard,\n\t}\n\tif r.retryHint != 0 {\n\t\tcandidates = append(\n\t\t\t[]lnwire.MilliSatoshi{r.retryHint}, candidates...,\n\t\t)\n\t}\n\n\ttried := make(map[lnwire.MilliSatoshi]bool)\n\tvar lastErr error\n\tfor _, shard := range candidates {\n\t\tif shard <= 0 || shard > amt || tried[shard] {\n\t\t\tcontinue\n\t\t}\n\t\ttried[shard] = true\n\n\t\tpath, _, err := r.findPath(\n\t\t\tshard, make(map[candidateEdgeKey]int),\n\t\t)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\trt, err := r.buildRoute(shard, path)\n\t\tif err != nil || !r.routeFeasible(rt) {\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserve(rt)\n\t\treturn rt, nil\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tinferred := amt\n\tremaining := edge.capacity - amt\n\tif remaining > 0 {\n\t\tinferred += remaining * 3 / 4\n\t}\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\tif inferred > preEstimate {\n\t\tpreEstimate = inferred\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt)\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tif belief.upperBad != 0 {\n\t\tbelief.upperBad = subtractFloor(belief.upperBad, amt)\n\t\tif belief.upperBad == 0 {\n\t\t\tbelief.upperBad = 1\n\t\t}\n\t}\n\n\tlikelyRemaining := lnwire.MilliSatoshi(0)\n\tif edge.capacity > amt {\n\t\tlikelyRemaining = (edge.capacity - amt) * 2 / 3\n\t}\n\tif belief.estimate < likelyRemaining {\n\t\tbelief.estimate = likelyRemaining\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\toutgoing := i + 1\n\t\tif outgoing < len(rt.Hops) {\n\t\t\treturn outgoing\n\t\t}\n\t\treturn -1\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.45\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > minimumShard {\n\t\t\tr.retryHint = delivered * 2 / 3\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.75\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(key, routeAmount(rt, failIndex))\n\t\tr.routePenalty[key] += riskCostMsat * 1.4\n\n\tdefault:\n\t\tr.routePenalty[key] += riskCostMsat * 0.75\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered > minimumShard {\n\t\tfactor := lnwire.MilliSatoshi(2)\n\t\tdivisor := lnwire.MilliSatoshi(3)\n\t\tif r.consecutiveFailures >= 3 {\n\t\t\tfactor = 1\n\t\t\tdivisor = 2\n\t\t}\n\n\t\tretry := delivered * factor / divisor\n\t\tif retry < minimumShard {\n\t\t\tretry = minimumShard\n\t\t}\n\t\tr.retryHint = retry\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 4,
"parent": 1,
"score": -0.15,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.68\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tretryCap lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, 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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc minMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[edge.key.chanID]\n\tspent := r.localSpent[edge.key.chanID]\n\tif spent >= available {\n\t\treturn 0\n\t}\n\tavailable -= spent\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tweight := 0.48\n\tif belief.lowerOK != 0 || belief.upperBad != 0 {\n\t\tweight = 0.64\n\t}\n\n\treturn clampProbability((1-weight)*prior + weight*point)\n}\n\nfunc (r *candidateRouter) edgeSafeLimit(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tif r.blocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tlimit := edge.capacity\n\tif edge.maxHTLC != 0 && edge.maxHTLC < limit {\n\t\tlimit = edge.maxHTLC\n\t}\n\n\tif edge.key.from == r.source {\n\t\tlimit = minMSat(limit, r.localAvailable(edge))\n\t\tif limit < edge.minHTLC {\n\t\t\treturn 0\n\t\t}\n\t\treturn limit\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= limit {\n\t\treturn 0\n\t}\n\tlimit -= reserved\n\n\tbelief := r.beliefs[edge.key]\n\tsafe := limit * 72 / 100\n\n\tif belief.estimate != 0 {\n\t\tsafe = belief.estimate * 90 / 100\n\t}\n\tif belief.upperBad != 0 {\n\t\tretry := belief.upperBad * 68 / 100\n\t\tsafe = maxMSat(safe, retry)\n\t}\n\tif belief.lowerOK != 0 {\n\t\tsafe = maxMSat(safe, belief.lowerOK)\n\t}\n\n\tif belief.upperBad != 0 && safe >= belief.upperBad {\n\t\tsafe = belief.upperBad - 1\n\t}\n\tif safe > limit {\n\t\tsafe = limit\n\t}\n\tif safe < edge.minHTLC {\n\t\treturn 0\n\t}\n\n\treturn safe\n}\n\ntype widestItem struct {\n\tnode route.Vertex\n\twidth lnwire.MilliSatoshi\n}\n\ntype widestQueue []*widestItem\n\nfunc (q widestQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q widestQueue) Less(i, j int) bool {\n\treturn q[i].width > q[j].width\n}\n\nfunc (q widestQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *widestQueue) Push(value any) {\n\t*q = append(*q, value.(*widestItem))\n}\n\nfunc (q *widestQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) widestAmount(\n\tmaximum lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif maximum <= 0 {\n\t\treturn 0\n\t}\n\n\twidth := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: maximum,\n\t}\n\tpq := &widestQueue{}\n\theap.Push(pq, &widestItem{\n\t\tnode: r.spec.Target,\n\t\twidth: maximum,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*widestItem)\n\t\tif item.width != width[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\treturn item.width\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tlimit := r.edgeSafeLimit(edge)\n\t\t\tif limit == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcandidate := minMSat(item.width, limit)\n\t\t\tif candidate <= width[edge.key.from] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\twidth[edge.key.from] = candidate\n\t\t\theap.Push(pq, &widestItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\twidth: candidate,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn 0\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tif reserved >= edge.capacity ||\n\t\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t} else if sending > r.localAvailable(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\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\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.retryCap = 0\n\t}\n\tr.lastRemaining = amt\n\n\tfor key, penalty := range r.routePenalty {\n\t\tpenalty *= 0.90\n\t\tif penalty < 1 {\n\t\t\tdelete(r.routePenalty, key)\n\t\t} else {\n\t\t\tr.routePenalty[key] = penalty\n\t\t}\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\tpartsLeft = 1\n\t}\n\n\tdesired := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\tif partsLeft == 1 {\n\t\tdesired = amt\n\t}\n\n\twidest := r.widestAmount(amt)\n\tshard := desired\n\tif widest != 0 {\n\t\tsafe := widest * 92 / 100\n\t\tif safe < minShardMsat {\n\t\t\tsafe = minMSat(widest, minShardMsat)\n\t\t}\n\t\tshard = minMSat(shard, safe)\n\t}\n\n\tif r.retryCap != 0 && shard > r.retryCap {\n\t\tshard = r.retryCap\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tminShard := minShardMsat\n\tif amt < minShard {\n\t\tminShard = amt\n\t}\n\tif shard < minShard {\n\t\tshard = minShard\n\t}\n\n\tvar lastErr error\n\ttried := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor shard >= minShard {\n\t\tif !tried[shard] {\n\t\t\ttried[shard] = true\n\t\t\trt, err := r.findRoute(shard)\n\t\t\tif err == nil {\n\t\t\t\tr.reserve(rt)\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t\tlastErr = err\n\t\t}\n\n\t\tif shard == minShard {\n\t\t\tbreak\n\t\t}\n\n\t\tnextShard := lnwire.MilliSatoshi(\n\t\t\tfloat64(shard) * lowerRetryFactor,\n\t\t)\n\t\tif nextShard >= shard {\n\t\t\tnextShard = shard - 1\n\t\t}\n\t\tif nextShard < minShard {\n\t\t\tnextShard = minShard\n\t\t}\n\t\tshard = nextShard\n\t}\n\n\tif !tried[amt] && partsLeft == 1 {\n\t\trt, err := r.findRoute(amt)\n\t\tif err == nil {\n\t\t\tr.reserve(rt)\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tif edge.capacity > amt {\n\t\testimate += (edge.capacity - amt) * 3 / 4\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\n\tlower := maxMSat(belief.lowerOK, amt)\n\tif lower > amt {\n\t\tbelief.lowerOK = lower - amt\n\t} else {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\testimate := maxMSat(belief.estimate, inferred)\n\tif estimate > amt {\n\t\tbelief.estimate = estimate - amt\n\t} else {\n\t\tbelief.estimate = 0\n\t}\n\n\tif belief.upperBad != 0 {\n\t\tif belief.upperBad > amt {\n\t\t\tbelief.upperBad -= amt\n\t\t} else {\n\t\t\tbelief.upperBad = 0\n\t\t}\n\t}\n\n\tif belief.estimate > edge.capacity {\n\t\tbelief.estimate = edge.capacity\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 35 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\t\tr.consecutiveFailures = 0\n\t\tr.retryCap = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 3\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := delivered * 3 / 4\n\t\tif retry > 0 &&\n\t\t\t(r.retryCap == 0 || retry < r.retryCap) {\n\n\t\t\tr.retryCap = retry\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.50\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmt := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmt)\n\t\tr.routePenalty[key] += riskCostMsat\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif retry > 0 &&\n\t\t\t(r.retryCap == 0 || retry < r.retryCap) {\n\n\t\t\tr.retryCap = retry\n\t\t}\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 3 {\n\t\tfor key, penalty := range r.routePenalty {\n\t\t\tr.routePenalty[key] = penalty * 0.72\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 5,
"parent": 2,
"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 (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 320000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tdiversityCost = 2200000.0\n\tminimumShard = lnwire.MilliSatoshi(10_000)\n\tmaxPlanCorridors = 8\n\tmaxPathHops = 24\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype pathCandidate struct {\n\tedges []*candidateEdge\n\thardLimit lnwire.MilliSatoshi\n\tlikelyLimit lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n\tretryHint lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\n\treturn (a + b - 1) / b\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amount >= value {\n\t\treturn 0\n\t}\n\n\treturn value - amount\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.026)\n\thighMode := 0.475 / (1 + math.Exp(15*(x-0.79)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\tavailable = subtractFloor(available, r.localSpent[key.chanID])\n\tavailable = subtractFloor(available, r.reserved[key])\n\n\treturn available\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.055, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.43*prior + 0.57*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n\thops 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].hops < q[j].hops\n\t}\n\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\n\treturn item\n}\n\nfunc (r *candidateRouter) edgeCost(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]int) float64 {\n\n\tp := r.probability(edge, amt)\n\tcost := float64(fee) - math.Log(p)*riskCostMsat + 500\n\n\tif edge.capacity > 0 {\n\t\tload := float64(amt) / float64(edge.capacity)\n\t\tcost += 22000 * load * load\n\t}\n\n\tcost += r.routePenalty[edge.key]\n\tif count := diversity[edge.key]; count != 0 {\n\t\tcost += float64(count) * diversityCost\n\t}\n\n\tif cost < 1 {\n\t\treturn 1\n\t}\n\n\treturn cost\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]int) ([]*candidateEdge, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\thopCount := map[route.Vertex]int{\n\t\tr.spec.Target: 0,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tsettled := make(map[route.Vertex]bool)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score != bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif settled[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tsettled[item.node] = true\n\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxPathHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.spec.Target {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif settled[edge.key.from] {\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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 && amtOver >= belief.upperBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tsending := amtOver\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tscore := item.score + r.edgeCost(\n\t\t\t\tedge, amtOver, fee, diversity,\n\t\t\t)\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\thopCount[edge.key.from] = item.hops + 1\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t\thops: item.hops + 1,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvisited := make(map[route.Vertex]bool)\n\tpath := make([]*candidateEdge, 0, hopCount[r.source])\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, 0, errors.New(\"route contains a cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, 0, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > maxPathHops {\n\t\t\treturn nil, 0, errors.New(\"route exceeds hop limit\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 0, errors.New(\"empty route\")\n\t}\n\n\treturn path, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.usable(amtOver[i]) {\n\t\t\treturn nil, errors.New(\"path cannot carry amount\")\n\t\t}\n\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeFeasible(rt *route.Route) bool {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok || r.blocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tamt := routeAmount(rt, i)\n\t\tif edge == nil || !edge.usable(amt) {\n\t\t\treturn false\n\t\t}\n\n\t\tif edge.key.from == r.source {\n\t\t\tif amt > r.localAvailable(key) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\tif reserved >= edge.capacity ||\n\t\t\tamt > edge.capacity-reserved {\n\n\t\t\treturn false\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) credibleAmount(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tif edge.key.from == r.source {\n\t\treturn r.localAvailable(edge.key)\n\t}\n\n\tlimit := edge.capacity\n\tif edge.maxHTLC != 0 && edge.maxHTLC < limit {\n\t\tlimit = edge.maxHTLC\n\t}\n\n\treserved := r.reserved[edge.key]\n\tlimit = subtractFloor(limit, reserved)\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && belief.upperBad-1 < limit {\n\t\tlimit = belief.upperBad - 1\n\t}\n\n\tif belief.estimate != 0 {\n\t\tlikely := belief.estimate * 92 / 100\n\t\tif belief.lowerOK > likely {\n\t\t\tlikely = belief.lowerOK\n\t\t}\n\t\tif likely < limit {\n\t\t\tlimit = likely\n\t\t}\n\n\t\treturn limit\n\t}\n\n\tpriorLimit := edge.capacity * 72 / 100\n\tpriorLimit = subtractFloor(priorLimit, reserved)\n\tif priorLimit < limit {\n\t\tlimit = priorLimit\n\t}\n\n\treturn limit\n}\n\nfunc (r *candidateRouter) routeLikely(rt *route.Route) bool {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tif edge == nil || routeAmount(rt, i) > r.credibleAmount(edge) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) amountLimit(path []*candidateEdge,\n\tmaxAmount lnwire.MilliSatoshi, likely bool) lnwire.MilliSatoshi {\n\n\tif maxAmount <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := maxAmount\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\trt, err := r.buildRoute(mid, path)\n\t\tok := err == nil && r.routeFeasible(rt)\n\t\tif ok && likely {\n\t\t\tok = r.routeLikely(rt)\n\t\t}\n\n\t\tif ok {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc pathID(path []*candidateEdge) string {\n\tid := \"\"\n\tfor _, edge := range path {\n\t\tid += fmt.Sprintf(\n\t\t\t\"%d:%x:%x/\", edge.key.chanID, edge.key.from,\n\t\t\tedge.key.to,\n\t\t)\n\t}\n\n\treturn id\n}\n\nfunc (r *candidateRouter) pathProbability(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\tprobability := 1.0\n\tfor i, edge := range path {\n\t\tprobability *= r.probability(edge, routeAmount(rt, i))\n\t}\n\n\treturn probability\n}\n\nfunc (r *candidateRouter) discoveryProbe(remaining\n\tlnwire.MilliSatoshi, parts uint32) lnwire.MilliSatoshi {\n\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\n\tprobe := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(parts)*8,\n\t)\n\tif probe < 100_000 {\n\t\tprobe = 100_000\n\t}\n\tif probe > 25_000_000 {\n\t\tprobe = 25_000_000\n\t}\n\tif probe > remaining {\n\t\tprobe = remaining\n\t}\n\n\treturn probe\n}\n\nfunc (r *candidateRouter) planPaths(remaining lnwire.MilliSatoshi,\n\tparts uint32) []pathCandidate {\n\n\tcount := int(parts)\n\tif count < 1 {\n\t\tcount = 1\n\t}\n\tif count > maxPlanCorridors {\n\t\tcount = maxPlanCorridors\n\t}\n\n\tprobes := []lnwire.MilliSatoshi{\n\t\tr.discoveryProbe(remaining, parts),\n\t\t100_000,\n\t\tminimumShard,\n\t}\n\n\tdiversity := make(map[candidateEdgeKey]int)\n\tseen := make(map[string]bool)\n\tplans := make([]pathCandidate, 0, count)\n\tmaxSearches := count * 5\n\n\tfor search := 0; search < maxSearches &&\n\t\tlen(plans) < count; search++ {\n\n\t\tvar (\n\t\t\tpath []*candidateEdge\n\t\t\tscore float64\n\t\t\terr error\n\t\t)\n\n\t\tfor _, probe := range probes {\n\t\t\tif probe <= 0 || probe > remaining {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath, score, err = r.findPath(probe, diversity)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tid := pathID(path)\n\t\tfor _, edge := range path {\n\t\t\tdiversity[edge.key]++\n\t\t}\n\t\tif seen[id] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[id] = true\n\n\t\thardLimit := r.amountLimit(path, remaining, false)\n\t\tif hardLimit <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tlikelyLimit := r.amountLimit(path, hardLimit, true)\n\t\tif likelyLimit < minimumShard {\n\t\t\tlikelyLimit = hardLimit\n\t\t\tif likelyLimit > 1_000_000 {\n\t\t\t\tlikelyLimit = 1_000_000\n\t\t\t}\n\t\t}\n\n\t\tplans = append(plans, pathCandidate{\n\t\t\tedges: path,\n\t\t\thardLimit: hardLimit,\n\t\t\tlikelyLimit: likelyLimit,\n\t\t\tscore: score,\n\t\t})\n\t}\n\n\tsort.SliceStable(plans, func(i, j int) bool {\n\t\tpi := r.pathProbability(\n\t\t\tplans[i].edges, plans[i].likelyLimit,\n\t\t)\n\t\tpj := r.pathProbability(\n\t\t\tplans[j].edges, plans[j].likelyLimit,\n\t\t)\n\t\tif math.Abs(pi-pj) > 0.04 {\n\t\t\treturn pi > pj\n\t\t}\n\t\tif plans[i].likelyLimit != plans[j].likelyLimit {\n\t\t\treturn plans[i].likelyLimit > plans[j].likelyLimit\n\t\t}\n\n\t\treturn plans[i].score < plans[j].score\n\t})\n\n\treturn plans\n}\n\nfunc sumPlanLimits(plans []pathCandidate, start, count int,\n\tlikely bool) lnwire.MilliSatoshi {\n\n\ttotal := lnwire.MilliSatoshi(0)\n\tend := start + count\n\tif end > len(plans) {\n\t\tend = len(plans)\n\t}\n\n\tfor i := start; i < end; i++ {\n\t\tif likely {\n\t\t\ttotal += plans[i].likelyLimit\n\t\t} else {\n\t\t\ttotal += plans[i].hardLimit\n\t\t}\n\t}\n\n\treturn total\n}\n\nfunc plannedAllocation(remaining lnwire.MilliSatoshi,\n\tparts uint32, plans []pathCandidate) lnwire.MilliSatoshi {\n\n\tif len(plans) == 0 || parts == 0 {\n\t\treturn 0\n\t}\n\n\tusable := len(plans)\n\tif usable > int(parts) {\n\t\tusable = int(parts)\n\t}\n\n\tfirstLimit := plans[0].likelyLimit\n\ttotalLikely := sumPlanLimits(plans, 0, usable, true)\n\tif totalLikely < remaining {\n\t\tfirstLimit = plans[0].hardLimit\n\t}\n\n\totherCapacity := sumPlanLimits(plans, 1, usable-1, true)\n\tif totalLikely < remaining {\n\t\totherCapacity = sumPlanLimits(\n\t\t\tplans, 1, usable-1, false,\n\t\t)\n\t}\n\n\trequiredNow := subtractFloor(remaining, otherCapacity)\n\tallocation := requiredNow\n\n\ttotalWeight := totalLikely\n\tif totalWeight < remaining {\n\t\ttotalWeight = sumPlanLimits(plans, 0, usable, false)\n\t}\n\tif totalWeight > 0 {\n\t\tweighted := lnwire.MilliSatoshi(\n\t\t\tfloat64(remaining) *\n\t\t\t\tfloat64(firstLimit) / float64(totalWeight),\n\t\t)\n\t\tif weighted > allocation {\n\t\t\tallocation = weighted\n\t\t}\n\t}\n\n\tif parts == 1 {\n\t\tallocation = remaining\n\t}\n\tif allocation > firstLimit {\n\t\tallocation = firstLimit\n\t}\n\tif allocation > plans[0].hardLimit {\n\t\tallocation = plans[0].hardLimit\n\t}\n\tif allocation > remaining {\n\t\tallocation = remaining\n\t}\n\tif allocation < minimumShard && remaining >= minimumShard &&\n\t\tplans[0].hardLimit >= minimumShard {\n\n\t\tallocation = minimumShard\n\t}\n\n\treturn allocation\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) tryRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !r.routeFeasible(rt) {\n\t\treturn nil, errors.New(\"route is not feasible\")\n\t}\n\n\tr.reserve(rt)\n\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 {\n\t\treturn nil, errors.New(\"payment permits no parts\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tplans := r.planPaths(amt, partsLeft)\n\n\tif len(plans) != 0 {\n\t\tshard := plannedAllocation(amt, partsLeft, plans)\n\t\tif r.retryHint != 0 && shard > r.retryHint {\n\t\t\tshard = r.retryHint\n\t\t}\n\t\tif partsLeft == 1 {\n\t\t\tshard = amt\n\t\t}\n\n\t\tfor _, plan := range plans {\n\t\t\ttryAmt := shard\n\t\t\tif tryAmt > plan.hardLimit {\n\t\t\t\ttryAmt = plan.hardLimit\n\t\t\t}\n\t\t\tif tryAmt <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trt, err := r.tryRoute(tryAmt, plan.edges)\n\t\t\tif err == nil {\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t}\n\t}\n\n\tcandidates := []lnwire.MilliSatoshi{\n\t\tamt,\n\t\tceilDiv(amt, lnwire.MilliSatoshi(partsLeft)),\n\t\tamt * 3 / 4,\n\t\tamt * 2 / 3,\n\t\tamt / 2,\n\t\tamt / 3,\n\t\tamt / 4,\n\t\tminimumShard,\n\t}\n\tif r.retryHint != 0 {\n\t\tcandidates = append(\n\t\t\t[]lnwire.MilliSatoshi{r.retryHint}, candidates...,\n\t\t)\n\t}\n\n\ttried := make(map[lnwire.MilliSatoshi]bool)\n\tvar lastErr error\n\tfor _, shard := range candidates {\n\t\tif shard <= 0 || shard > amt || tried[shard] {\n\t\t\tcontinue\n\t\t}\n\t\tif partsLeft == 1 && shard != amt {\n\t\t\tcontinue\n\t\t}\n\t\ttried[shard] = true\n\n\t\tpath, _, err := r.findPath(\n\t\t\tshard, make(map[candidateEdgeKey]int),\n\t\t)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\trt, err := r.tryRoute(shard, path)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 4 / 5\n\t}\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 4 / 5\n\t}\n\tif inferred > preEstimate {\n\t\tpreEstimate = inferred\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt)\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tif belief.upperBad != 0 {\n\t\tbelief.upperBad = subtractFloor(belief.upperBad, amt)\n\t\tif belief.upperBad == 0 {\n\t\t\tbelief.upperBad = 1\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt / 4\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\toutgoing := i + 1\n\t\tif outgoing < len(rt.Hops) {\n\t\t\treturn outgoing\n\t\t}\n\n\t\treturn -1\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.15\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.65\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > minimumShard {\n\t\t\tr.retryHint = delivered / 2\n\t\t\tif r.retryHint < minimumShard {\n\t\t\t\tr.retryHint = minimumShard\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.65\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(key, routeAmount(rt, failIndex))\n\t\tr.routePenalty[key] += riskCostMsat * 1.8\n\n\tdefault:\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered > minimumShard {\n\t\tretry := delivered * 3 / 5\n\t\tif r.consecutiveFailures >= 2 {\n\t\t\tretry = delivered / 2\n\t\t}\n\t\tif r.consecutiveFailures >= 4 {\n\t\t\tretry = delivered / 3\n\t\t}\n\t\tif retry < minimumShard {\n\t\t\tretry = minimumShard\n\t\t}\n\t\tr.retryHint = retry\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 6,
"parent": 0,
"score": 0.7947,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype 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 <= 0 || 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\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\t// The first term models the small depleted-side tail. The second\n\t// models the large liquid-side mode and its cliff near capacity.\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.10\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.78, 0.22*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: deliver,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := item.amt + r.reserved[edge.key]\n\t\t\tif totalLiquidity > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotalLiquidity >\n\t\t\t\t\tr.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\t// Reliability dominates. A million-msat risk scale still\n\t\t\t// permits fees to break ties between similarly reliable paths.\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_000_000 +\n\t\t\t\t0.015 +\n\t\t\t\t0.32*float64(r.edgeFailures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 14)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\t// Failure bounds are useful shard breakpoints. Only retain the largest\n\t// few after deduplication to keep route search bounded.\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] || belief.upperFail <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tbound := belief.upperFail * 3 / 4\n\t\tif bound >= minimum && bound <= amt {\n\t\t\tbounds = append(bounds, bound)\n\t\t}\n\t}\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 4 {\n\t\tbounds = bounds[:4]\n\t}\n\tfor _, bound := range bounds {\n\t\taddCandidate(&values, seen, bound, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// This approximates joint route-set planning. It prices route\n\t\t// failure, fees, and the number of shards needed to cover the\n\t\t// remaining amount while reservations steer concurrent shards onto\n\t\t// distinct corridors.\n\t\tshardRatio := float64(amt) / float64(shard)\n\t\tchoice.utility = -math.Log(choice.probability) +\n\t\t\t0.30*math.Log(shardRatio) +\n\t\t\tfloat64(choice.fee)/2_000_000\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 3\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + amt/3) / 3\n\t}\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\treturn value - amount\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\t// Settlement moves liquidity away from this direction, so shift all\n\t// evidence by the amount that just traversed the channel.\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\t\tr.lastFailedAmt = 0\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\tfor _, key := range keys {\n\t\tr.edgeFailures[key]++\n\t}\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\treturn nil\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// A route using an advertised policy that is rejected for fees or\n\t\t// timelocks should not be retried during this payment.\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 7,
"parent": 3,
"score": 0.5021,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxRouteAttempts = 48\n\tmaxAmountChoices = 24\n\tmaxForecastSearch = 160\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, 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.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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, exists := sharedBeliefs.values[key]\n\t\tif !exists {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.10\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\tconfidence := math.Min(0.80, 0.20*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: deliver,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, exists := score[item.node]\n\t\tif !exists || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tneeded := required[item.node]\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(needed) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := needed + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, needed)\n\t\t\tedgeFee := edge.fee(needed)\n\t\t\tsending := needed + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = needed\n\t\t\t}\n\n\t\t\tfailurePenalty := 0.20 *\n\t\t\t\tfloat64(r.edgeFailures[edge.key])\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_000_000 +\n\t\t\t\t0.012 + failurePenalty\n\t\t\tcandidate := item.score + step\n\n\t\t\told, found := score[edge.key.from]\n\t\t\tif found && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc ceilDivide(value lnwire.MilliSatoshi,\n\tdivisor uint32) lnwire.MilliSatoshi {\n\n\td := lnwire.MilliSatoshi(divisor)\n\treturn (value + d - 1) / d\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := ceilDivide(amt, partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, maxAmountChoices)\n\n\t// Enumerate balanced and deliberately unequal allocations for every\n\t// feasible part count. Reservations make subsequent calls choose other\n\t// corridors, so these values act as the first shard of a joint plan.\n\tfor parts := uint32(1); parts <= partsLeft; parts++ {\n\t\tbase := ceilDivide(amt, parts)\n\t\taddAmount(&values, seen, base, minimum, amt)\n\n\t\tif parts > 1 {\n\t\t\taddAmount(\n\t\t\t\t&values, seen, base*9/8, minimum, amt,\n\t\t\t)\n\t\t\taddAmount(\n\t\t\t\t&values, seen, base*5/4, minimum, amt,\n\t\t\t)\n\t\t\taddAmount(\n\t\t\t\t&values, seen, base*3/2, minimum, amt,\n\t\t\t)\n\t\t}\n\t}\n\n\tif r.lastFailedAmt > 0 {\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt*4/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK >= minimum && belief.lowerOK <= amt {\n\t\t\tbounds = append(bounds, belief.lowerOK)\n\t\t}\n\t\tif belief.upperFail > 1 {\n\t\t\tvalue := belief.upperFail * 4 / 5\n\t\t\tif value >= minimum && value <= amt {\n\t\t\t\tbounds = append(bounds, value)\n\t\t\t}\n\t\t}\n\t\tif belief.estimate >= minimum && belief.estimate <= amt {\n\t\t\tbounds = append(bounds, belief.estimate*9/10)\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 8 {\n\t\tbounds = bounds[:8]\n\t}\n\tfor _, value := range bounds {\n\t\taddAmount(&values, seen, value, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\tif len(values) > maxAmountChoices {\n\t\tvalues = values[:maxAmountChoices]\n\t}\n\n\treturn values\n}\n\nfunc reserveChoice(reserved map[edgeKey]lnwire.MilliSatoshi,\n\tchoice *routeChoice) {\n\n\tfor i, key := range choice.keys {\n\t\treserved[key] += choice.amounts[i]\n\t}\n}\n\nfunc restoreReservations(destination,\n\tsnapshot map[edgeKey]lnwire.MilliSatoshi) {\n\n\tfor key := range destination {\n\t\tdelete(destination, key)\n\t}\n\tfor key, value := range snapshot {\n\t\tdestination[key] = value\n\t}\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, value := range source {\n\t\tresult[key] = value\n\t}\n\n\treturn result\n}\n\nfunc choiceCost(choice *routeChoice) float64 {\n\tp := choice.probability\n\tif p < 0.005 {\n\t\tp = 0.005\n\t}\n\n\t// The logarithmic term maximizes the chance that every planned shard\n\t// settles. The reciprocal term mildly prices the expected retry count.\n\treturn -math.Log(p) + 0.035/p +\n\t\tfloat64(choice.fee)/2_000_000\n}\n\nfunc (r *candidateRouter) forecastPlan(total lnwire.MilliSatoshi,\n\tpartsLeft uint32, first *routeChoice) (float64, bool) {\n\n\tif first.deliver <= 0 || first.deliver > total {\n\t\treturn 0, false\n\t}\n\n\tsnapshot := copyReservations(r.reserved)\n\tdefer restoreReservations(r.reserved, snapshot)\n\n\treserveChoice(r.reserved, first)\n\tremaining := total - first.deliver\n\tscore := choiceCost(first) + 0.025\n\tused := uint32(1)\n\n\tfor remaining > 0 {\n\t\tif used >= partsLeft {\n\t\t\treturn 0, false\n\t\t}\n\n\t\tslots := partsLeft - used\n\t\tshard := ceilDivide(remaining, slots)\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\t// Try to place more on an available corridor so that later\n\t\t\t// slots are not forced below their channel minimums.\n\t\t\tfound := false\n\t\t\tfor divisor := uint32(2); divisor <= slots; divisor++ {\n\t\t\t\tprobe := ceilDivide(remaining, divisor)\n\t\t\t\tchoice, err = r.findRoute(probe)\n\t\t\t\tif err == nil {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\treturn 0, false\n\t\t\t}\n\t\t}\n\n\t\treserveChoice(r.reserved, choice)\n\t\tremaining -= choice.deliver\n\t\tscore += choiceCost(choice) + 0.025\n\t\tused++\n\t}\n\n\t// Prefer fewer parts only when end-to-end reliability is comparable.\n\tscore += 0.012 * float64(used*used)\n\n\treturn score, true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxRouteAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\tbestScore := math.Inf(1)\n\tsearches := 0\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tif searches >= maxForecastSearch {\n\t\t\tbreak\n\t\t}\n\n\t\tchoice, err := r.findRoute(shard)\n\t\tsearches++\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tscore, feasible := r.forecastPlan(amt, partsLeft, choice)\n\t\tif !feasible {\n\t\t\tcontinue\n\t\t}\n\n\t\tif best == nil || score < bestScore {\n\t\t\tbest = choice\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\t// Preserve a reliable terminal fallback when the bounded joint\n\t\t// search cannot construct a complete portfolio.\n\t\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\t\tchoice, err := r.findRoute(shard)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := choiceCost(choice)\n\t\t\tif best == nil || score < bestScore {\n\t\t\t\tbest = choice\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treserveChoice(r.reserved, best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(key edgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 3\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + amt/3) / 3\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnForwarded(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= amt {\n\t\tbelief.upperFail = 0\n\t}\n\n\toptimistic := edge.capacity * 9 / 10\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\testimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif estimate < optimistic {\n\t\testimate = (estimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(estimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseReservations(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\tvalue := r.reserved[key]\n\t\tif value <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = value - amounts[i]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservations(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\t// Unknown failures receive only a small path-level penalty. They do\n\t\t// not justify poisoning every channel on the route.\n\t\tfor _, key := range keys {\n\t\t\tif r.edgeFailures[key] < 2 {\n\t\t\t\tr.edgeFailures[key]++\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Every channel before the failing outgoing channel accepted the HTLC.\n\t// The failed attempt rolls back, so this is a liquidity observation\n\t// without the balance shift associated with settlement.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnForwarded(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 8,
"parent": 1,
"score": 0.5085,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\triskCostMsat = 280000.0\n\n\tlowerRetryFactor = 0.62\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\tfailures uint16\n\tsuccess uint16\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tmu sync.Mutex\n\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 8\n\tcase spec.Amount > 400_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 100_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 25_000_000:\n\t\tparts = 2\n\t}\n\tif spec.MaxParts != 0 && parts > spec.MaxParts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\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.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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < minProbability:\n\t\treturn minProbability\n\tcase p > maxProbability:\n\t\treturn maxProbability\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.027)\n\thighMode := 0.47 / (1 + math.Exp(15*(x-0.80)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[edge.key.chanID]\n\tspent := r.localSpent[edge.key.chanID]\n\treserved := r.reserved[edge.key]\n\n\tif spent >= available {\n\t\treturn 0\n\t}\n\tavailable -= spent\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.055, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tif belief.failures > belief.success {\n\t\tweight := 0.72\n\t\tif belief.failures >= 3 {\n\t\t\tweight = 0.84\n\t\t}\n\t\treturn clampProbability(\n\t\t\t(1-weight)*prior + weight*point,\n\t\t)\n\t}\n\n\treturn clampProbability(0.48*prior + 0.52*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, exists := dist[item.node]\n\t\tif !exists || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\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\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tpenalty := r.routePenalty[edge.key]\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tpenalty + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, exists := r.edgeByKey[key]\n\treturn key, exists\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tdelivered := deliveredAmount(rt)\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeProbability(rt *route.Route) float64 {\n\tprobability := 1.0\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability *= r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t}\n\treturn probability\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendShard(values []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn values\n\t}\n\n\tseen[value] = true\n\treturn append(values, value)\n}\n\nfunc (r *candidateRouter) shardCandidates(amt,\n\tminimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 32)\n\n\tvalues = appendShard(values, seen, amt, minimum, amt)\n\tvalues = appendShard(\n\t\tvalues, seen, r.baseShard, minimum, amt,\n\t)\n\n\tfractions := []float64{\n\t\t0.82, 0.68, 0.56, 0.46, 0.38, 0.31, 0.25,\n\t\t0.20, 0.16, 0.125,\n\t}\n\tfor _, fraction := range fractions {\n\t\tvalue := lnwire.MilliSatoshi(\n\t\t\tfloat64(amt) * fraction,\n\t\t)\n\t\tvalues = appendShard(\n\t\t\tvalues, seen, value, minimum, amt,\n\t\t)\n\t}\n\n\tfor _, edge := range r.edgeByKey {\n\t\tvar limit lnwire.MilliSatoshi\n\t\tswitch {\n\t\tcase edge.key.from == r.source:\n\t\t\tlimit = r.localAvailable(edge)\n\n\t\tcase r.beliefs[edge.key].lowerOK != 0:\n\t\t\tlimit = r.beliefs[edge.key].lowerOK\n\n\t\tcase r.beliefs[edge.key].estimate != 0:\n\t\t\tlimit = r.beliefs[edge.key].estimate * 9 / 10\n\n\t\tcase r.beliefs[edge.key].upperBad != 0:\n\t\t\tlimit = r.beliefs[edge.key].upperBad * 3 / 5\n\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\tvalues = appendShard(\n\t\t\tvalues, seen, limit, minimum, amt,\n\t\t)\n\t\tvalues = appendShard(\n\t\t\tvalues, seen, limit*4/5, minimum, amt,\n\t\t)\n\t}\n\n\tvalues = appendShard(\n\t\tvalues, seen, minimum, minimum, amt,\n\t)\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tif r.spec.MaxParts != 0 &&\n\t\tinFlightHtlcs >= r.spec.MaxParts {\n\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tminimum := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\tif minimum < minShardMsat {\n\t\tminimum = minShardMsat\n\t}\n\tif minimum > amt {\n\t\tminimum = amt\n\t}\n\n\tcandidates := r.shardCandidates(amt, minimum)\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestUtility = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range candidates {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := r.routeProbability(rt)\n\t\tfee := float64(routeFee(rt))\n\t\tsizeGain := math.Log1p(\n\t\t\tfloat64(shard) / math.Max(float64(minimum), 1),\n\t\t)\n\n\t\tutility := 1.35*sizeGain +\n\t\t\t0.42*math.Log(math.Max(probability, 1e-12)) -\n\t\t\tfee/2_000_000\n\n\t\tif shard == amt {\n\t\t\tutility += 0.25\n\t\t}\n\t\tif probability >= 0.09 {\n\t\t\tutility += 0.18\n\t\t}\n\t\tif utility > bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbestRoute = rt\n\t\t}\n\n\t\tif shard > minimum && probability >= 0.22 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edgeByKey[key]\n\tif edge != nil {\n\t\tif belief.lowerOK > edge.capacity {\n\t\t\tbelief.lowerOK = edge.capacity\n\t\t}\n\t\tif belief.estimate > edge.capacity {\n\t\t\tbelief.estimate = edge.capacity\n\t\t}\n\t\tif belief.upperBad > edge.capacity {\n\t\t\tbelief.upperBad = edge.capacity\n\t\t}\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tremaining := edge.capacity - amt\n\tinferred := amt + remaining*4/5\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\tif belief.success < math.MaxUint16 {\n\t\tbelief.success++\n\t}\n\tif belief.failures > 0 {\n\t\tbelief.failures--\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc subtractFloor(value,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif amt >= value {\n\t\treturn 0\n\t}\n\treturn value - amt\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tremaining := edge.capacity - amt\n\tinferredPre := amt + remaining*4/5\n\tif inferredPre > belief.estimate {\n\t\tbelief.estimate = inferredPre\n\t}\n\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tbelief.estimate = subtractFloor(belief.estimate, amt)\n\tif belief.upperBad != 0 {\n\t\tbelief.upperBad = subtractFloor(\n\t\t\tbelief.upperBad, amt,\n\t\t)\n\t\tif belief.upperBad == 0 {\n\t\t\tbelief.upperBad = 1\n\t\t}\n\t}\n\tif belief.success < math.MaxUint16 {\n\t\tbelief.success++\n\t}\n\tif belief.failures > 0 {\n\t\tbelief.failures--\n\t}\n\tr.saveBelief(key, belief)\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edgeByKey[reverseKey]\n\tif reverseEdge == nil || reverseKey.from == r.source {\n\t\treturn\n\t}\n\n\treverse := r.beliefs[reverseKey]\n\tif reverse.estimate > reverseEdge.capacity-amt {\n\t\treverse.estimate = reverseEdge.capacity\n\t} else {\n\t\treverse.estimate += amt\n\t}\n\tif reverse.lowerOK != 0 {\n\t\tif reverse.lowerOK > reverseEdge.capacity-amt {\n\t\t\treverse.lowerOK = reverseEdge.capacity\n\t\t} else {\n\t\t\treverse.lowerOK += amt\n\t\t}\n\t}\n\tif reverse.upperBad != 0 {\n\t\tif reverse.upperBad > reverseEdge.capacity-amt {\n\t\t\treverse.upperBad = 0\n\t\t} else {\n\t\t\treverse.upperBad += amt\n\t\t}\n\t}\n\tr.saveBelief(reverseKey, reverse)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt / 5\n\tdepletedEstimate := edge.capacity / 25\n\tif estimate > depletedEstimate {\n\t\testimate = depletedEstimate\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\tif belief.failures < math.MaxUint16 {\n\t\tbelief.failures++\n\t}\n\tif belief.success > 0 {\n\t\tbelief.success--\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif !exists {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.18\n\t\t}\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > 0 {\n\t\t\tremaining := r.lastRemaining - delivered\n\t\t\tif remaining > 0 {\n\t\t\t\ttarget := delivered\n\t\t\t\tif target > remaining {\n\t\t\t\t\ttarget = remaining\n\t\t\t\t}\n\t\t\t\tr.baseShard = target\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif exists {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 3\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.7\n\t\t}\n\t}\n\n\tkey, exists := r.routeEdge(rt, failIndex)\n\tif !exists {\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmt := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmt)\n\t\tr.routePenalty[key] += riskCostMsat * 1.4\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tretry := lnwire.MilliSatoshi(\n\t\tfloat64(delivered) * lowerRetryFactor,\n\t)\n\tif retry > 0 && retry < r.baseShard {\n\t\tr.baseShard = retry\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 9,
"parent": 1,
"score": -0.1122,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskCostMsat = 260000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\n\tlowerRetryFactor = 0.62\n\tmaxShardFactor = 2.20\n\tmaxShardProbes = 28\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tretryHint lnwire.MilliSatoshi\n\tfailureCount uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\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.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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < minProbability:\n\t\treturn minProbability\n\tcase p > maxProbability:\n\t\treturn maxProbability\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.024)\n\thighMode := 0.48 / (1 + math.Exp(15*(x-0.80)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) available(edge *candidateEdge) lnwire.MilliSatoshi {\n\tavailable := edge.capacity\n\tif edge.key.from == r.source {\n\t\tavailable = r.localBalances[edge.key.chanID]\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif amt > r.available(edge) {\n\t\treturn minProbability\n\t}\n\tif edge.key.from == r.source {\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.065, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.52*prior + 0.48*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoutePass(amt lnwire.MilliSatoshi,\n\tallowKnownBad bool) (*route.Route, error) {\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, exists := dist[item.node]\n\t\tif !exists || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif required[item.node] != item.arriving {\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.blocked[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) || amtOver > r.available(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tknownBad := belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad\n\t\t\tif knownBad && !allowKnownBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tif fee < 0 || sending > math.MaxInt64-fee {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\n\t\t\tstaleProbePenalty := 0.0\n\t\t\tif knownBad {\n\t\t\t\tstaleProbePenalty = riskCostMsat * 2.4\n\t\t\t}\n\n\t\t\tcapacityBonus := 0.0\n\t\t\tif edge.capacity > 0 {\n\t\t\t\tspare := float64(r.available(edge)-amtOver) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tif spare > 0 {\n\t\t\t\t\tcapacityBonus = math.Min(\n\t\t\t\t\t\t45000, spare*18000,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + staleProbePenalty +\n\t\t\t\t250 - capacityBonus\n\n\t\t\toldScore, seen := dist[edge.key.from]\n\t\t\tif seen && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\trt, err := r.findRoutePass(amt, false)\n\tif err == nil {\n\t\treturn rt, nil\n\t}\n\n\t// Hard liquidity bounds are intentionally retained across payments.\n\t// If they eliminate every path, probe the least costly stale path once\n\t// instead of terminally giving up without refreshing the evidence.\n\treturn r.findRoutePass(amt, true)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tfee := outgoing.fee(amtOver[i+1])\n\t\tif fee < 0 || amtOver[i+1] > math.MaxInt64-fee {\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, exists := r.edgeByKey[key]\n\n\treturn key, exists\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendProbe(probes []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn probes\n\t}\n\n\tseen[value] = true\n\treturn append(probes, value)\n}\n\nfunc (r *candidateRouter) shardProbes(remaining,\n\tminimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tmaximum := remaining\n\tif minimum < remaining {\n\t\tsoftMax := lnwire.MilliSatoshi(\n\t\t\tfloat64(minimum) * maxShardFactor,\n\t\t)\n\t\tif softMax > minimum && softMax < maximum {\n\t\t\tmaximum = softMax\n\t\t}\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tprobes := make([]lnwire.MilliSatoshi, 0, maxShardProbes)\n\n\tprobes = appendProbe(probes, seen, minimum, minimum, maximum)\n\tfor _, factor := range []float64{\n\t\t1.12, 1.28, 1.48, 1.72, 2.00, 2.20,\n\t} {\n\t\tprobes = appendProbe(\n\t\t\tprobes, seen,\n\t\t\tlnwire.MilliSatoshi(float64(minimum)*factor),\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tif r.retryHint != 0 {\n\t\tprobes = appendProbe(\n\t\t\tprobes, seen, r.retryHint, minimum, maximum,\n\t\t)\n\t}\n\n\tfor _, belief := range r.beliefs {\n\t\tif belief.lowerOK != 0 {\n\t\t\tfor _, factor := range []float64{0.96, 0.82} {\n\t\t\t\tprobes = appendProbe(\n\t\t\t\t\tprobes, seen,\n\t\t\t\t\tlnwire.MilliSatoshi(\n\t\t\t\t\t\tfloat64(belief.lowerOK) *\n\t\t\t\t\t\t\tfactor,\n\t\t\t\t\t),\n\t\t\t\t\tminimum, maximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tif belief.upperBad != 0 {\n\t\t\tfor _, factor := range []float64{0.72, 0.55} {\n\t\t\t\tprobes = appendProbe(\n\t\t\t\t\tprobes, seen,\n\t\t\t\t\tlnwire.MilliSatoshi(\n\t\t\t\t\t\tfloat64(belief.upperBad) *\n\t\t\t\t\t\t\tfactor,\n\t\t\t\t\t),\n\t\t\t\t\tminimum, maximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tprobes = appendProbe(\n\t\tprobes, seen, maximum, minimum, maximum,\n\t)\n\tsort.Slice(probes, func(i, j int) bool {\n\t\treturn probes[i] < probes[j]\n\t})\n\n\tif len(probes) <= maxShardProbes {\n\t\treturn probes\n\t}\n\n\ttrimmed := make([]lnwire.MilliSatoshi, 0, maxShardProbes)\n\tfor i, probe := range probes {\n\t\tif len(trimmed) == maxShardProbes {\n\t\t\tbreak\n\t\t}\n\n\t\tremainingSlots := maxShardProbes - len(trimmed)\n\t\tremainingItems := len(probes) - i\n\t\tif i == 0 || i == len(probes)-1 ||\n\t\t\tremainingItems <= remainingSlots ||\n\t\t\ti%(len(probes)/maxShardProbes+1) == 0 {\n\n\t\t\ttrimmed = append(trimmed, probe)\n\t\t}\n\t}\n\n\treturn trimmed\n}\n\nfunc (r *candidateRouter) routeQuality(rt *route.Route,\n\tdelivered, minimum lnwire.MilliSatoshi) float64 {\n\n\tlogProbability := 0.0\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tlogProbability += math.Log(\n\t\t\tr.probability(edge, routeAmount(rt, i)),\n\t\t)\n\t}\n\n\tfee := rt.TotalAmount - delivered\n\tsizeGain := math.Log(\n\t\tmath.Max(1, float64(delivered)/float64(minimum)),\n\t)\n\n\t// Reliability dominates. The size term rewards a proven high-liquidity\n\t// corridor enough to create unequal MPP shards, while avoiding a blind\n\t// preference for the largest gossip-feasible amount.\n\treturn logProbability + 0.34*sizeGain -\n\t\tfloat64(fee)/riskCostMsat\n}\n\nfunc (r *candidateRouter) chooseRoute(remaining,\n\tminimum lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tprobes := r.shardProbes(remaining, minimum)\n\n\tvar (\n\t\tbest *route.Route\n\t\tbestQuality = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range probes {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tquality := r.routeQuality(rt, shard, minimum)\n\t\tif best == nil || quality > bestQuality {\n\t\t\tbest = rt\n\t\t\tbestQuality = quality\n\t\t}\n\t}\n\n\tif best != nil {\n\t\treturn best, nil\n\t}\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.failureCount = 0\n\t\tr.retryHint = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tminimum := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\tif minimum <= 0 || minimum > amt {\n\t\tminimum = amt\n\t}\n\n\trt, err := r.chooseRoute(amt, minimum)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.reserve(rt)\n\treturn rt, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tif edge.capacity > amt {\n\t\testimate += (edge.capacity - amt) * 4 / 5\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 28 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif !exists {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.failureCount = 0\n\t\tr.retryHint = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif exists {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.45\n\t\t\t}\n\t\t}\n\n\t\tr.failureCount++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.65\n\t\t}\n\t}\n\n\tkey, exists := r.routeEdge(rt, failIndex)\n\tif !exists {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tfailedAmount := routeAmount(rt, failIndex)\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tr.recordFailure(key, failedAmount)\n\t\tr.routePenalty[key] += riskCostMsat\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif retry > 0 &&\n\t\t\t(r.retryHint == 0 || retry < r.retryHint) {\n\n\t\t\tr.retryHint = retry\n\t\t}\n\t}\n\n\tr.failureCount++\n\tif r.failureCount >= 3 {\n\t\tr.routePenalty[key] += riskCostMsat * 0.35\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 10,
"parent": 3,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype 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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tmu sync.Mutex\n\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"payment specification is nil\")\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[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity * 9 / 10\n\t\t}\n\n\t\tif belief.estimate > edge.capacity {\n\t\t\tbelief.estimate = edge.capacity\n\t\t}\n\t\tif belief.lowerOK > edge.capacity {\n\t\t\tbelief.lowerOK = edge.capacity\n\t\t}\n\t\tif belief.upperFail > edge.capacity {\n\t\t\tbelief.upperFail = 0\n\t\t}\n\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\n\tcase p > 0.985:\n\t\treturn 0.985\n\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.82, 0.20*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := item.amt + r.reserved[edge.key]\n\t\t\tif totalLiquidity > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotalLiquidity > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tfailurePenalty := 0.16 *\n\t\t\t\tmath.Min(4, float64(r.edgeFailures[edge.key]))\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 + failurePenalty\n\n\t\t\tcandidate := item.score + step\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(\n\t\t\tr.edges[key], amounts[i],\n\t\t)\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: clampRouteProbability(probability),\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc clampRouteProbability(p float64) float64 {\n\tif p < 0.0001 {\n\t\treturn 0.0001\n\t}\n\tif p > 0.999 {\n\t\treturn 0.999\n\t}\n\n\treturn p\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\n\t\t\t\t\"route contains a cycle\",\n\t\t\t)\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\tif minimum > amt {\n\t\tminimum = amt\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 18)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt*3/5, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt*2/5, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*7/8,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK >= minimum && belief.lowerOK <= amt {\n\t\t\tbounds = append(bounds, belief.lowerOK)\n\t\t}\n\n\t\tif belief.upperFail > 1 {\n\t\t\tsafe := belief.upperFail * 7 / 8\n\t\t\tif safe >= minimum && safe <= amt {\n\t\t\t\tbounds = append(bounds, safe)\n\t\t\t}\n\t\t}\n\n\t\tif belief.estimate >= minimum && belief.estimate <= amt {\n\t\t\tbounds = append(bounds, belief.estimate*7/8)\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 6 {\n\t\tbounds = bounds[:6]\n\t}\n\n\tfor _, bound := range bounds {\n\t\taddCandidate(&values, seen, bound, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc choiceUtility(choice *routeChoice, remaining,\n\tshard lnwire.MilliSatoshi) float64 {\n\n\tprobability := clampRouteProbability(choice.probability)\n\tratio := float64(remaining) / float64(shard)\n\n\texpectedAttempts := 1 / probability\n\tpartCost := 0.22 * math.Max(0, ratio-1)\n\tfeeCost := float64(choice.fee) / 2_000_000\n\n\treturn expectedAttempts + partCost + feeCost\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tchoice.utility = choiceUtility(choice, amt, shard)\n\t\tif best == nil ||\n\t\t\tchoice.utility < best.utility-1e-12 ||\n\t\t\t(math.Abs(choice.utility-best.utility) <= 1e-12 &&\n\t\t\t\tchoice.deliver > best.deliver) {\n\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\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\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tfailedEstimate := amt / 4\n\tswitch {\n\tcase belief.estimate == 0:\n\t\tbelief.estimate = failedEstimate\n\n\tcase belief.estimate >= amt:\n\t\tbelief.estimate = (\n\t\t\tbelief.estimate + 2*failedEstimate\n\t\t) / 3\n\n\tdefault:\n\t\tbelief.estimate = (\n\t\t\t2*belief.estimate + failedEstimate\n\t\t) / 3\n\t}\n\n\tif belief.estimate > edge.capacity {\n\t\tbelief.estimate = edge.capacity\n\t}\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnAvailable(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\toptimistic := edge.capacity * 9 / 10\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (\n\t\t\t2*belief.estimate + optimistic\n\t\t) / 3\n\t}\n\n\tif belief.upperFail > 0 && belief.upperFail <= belief.lowerOK {\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSettlement(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) releaseReservations(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservations(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSettlement(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.lastFailedAmt = delivered\n\n\tfailIndex := failureEdgeIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tif r.edgeFailures[key] < 8 {\n\t\t\t\tr.edgeFailures[key]++\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey := keys[i]\n\t\tr.learnAvailable(key, amounts[i])\n\n\t\tif r.edgeFailures[key] > 0 {\n\t\t\tr.edgeFailures[key]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tif r.edgeFailures[key] < 8 {\n\t\tr.edgeFailures[key] += 2\n\t}\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 11,
"parent": 3,
"score": 0.6389,
"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\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxRouteAttempts = 64\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\trouteFailures map[string]uint32\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t\trouteFailures: make(map[string]uint32),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = normalizeBelief(belief, edge.capacity)\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc normalizeBelief(b liquidityBelief,\n\tcapacity lnwire.MilliSatoshi) liquidityBelief {\n\n\tif b.lowerOK > capacity {\n\t\tb.lowerOK = capacity\n\t}\n\tif b.upperFail > capacity {\n\t\tb.upperFail = 0\n\t}\n\tif b.estimate > capacity {\n\t\tb.estimate = capacity\n\t}\n\tif b.upperFail > 0 && b.lowerOK >= b.upperFail {\n\t\tb.lowerOK = b.upperFail - 1\n\t}\n\tif b.lowerOK < 0 {\n\t\tb.lowerOK = 0\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\testimate := belief.estimate\n\tif estimate <= 0 {\n\t\testimate = edge.capacity / 2\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(estimate))/scale,\n\t))\n\tconfidence := math.Min(0.82, 0.18*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tsignature string\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: deliver,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tneeded := required[item.node]\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(needed) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := needed + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, needed)\n\t\t\tedgeFee := edge.fee(needed)\n\t\t\tsending := needed + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = needed\n\t\t\t}\n\n\t\t\tfailurePenalty := 0.20 *\n\t\t\t\tmath.Log1p(float64(r.edgeFailures[edge.key]))\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 + failurePenalty\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old-1e-12 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t\tsignature: routeSignature(keys),\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc routeSignature(keys []edgeKey) string {\n\tvar builder strings.Builder\n\tfor _, key := range keys {\n\t\tbuilder.WriteString(strconv.FormatUint(key.chanID, 10))\n\t\tbuilder.WriteByte('/')\n\t}\n\n\treturn builder.String()\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\tif minimum > amt {\n\t\tminimum = amt\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, int(partsLeft)+18)\n\n\t// Evaluate every equal-part plan. This lets the route search jointly\n\t// choose the effective part count instead of relying on a halving ladder.\n\tfor parts := uint32(1); parts <= partsLeft; parts++ {\n\t\tshard := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\t\tlnwire.MilliSatoshi(parts)\n\t\taddCandidate(&values, seen, shard, minimum, amt)\n\t}\n\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*4/5, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt*3/5, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*7/8,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\ttype breakpoint struct {\n\t\tvalue lnwire.MilliSatoshi\n\t\tscore float64\n\t}\n\n\tbreakpoints := make([]breakpoint, 0, len(r.beliefs)*2)\n\tfor key, belief := range r.beliefs {\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.upperFail > 1 {\n\t\t\tvalue := belief.upperFail - 1\n\t\t\tif value >= minimum && value <= amt {\n\t\t\t\tbreakpoints = append(breakpoints, breakpoint{\n\t\t\t\t\tvalue: value,\n\t\t\t\t\tscore: float64(value) * 2,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tvalue = belief.upperFail * 3 / 4\n\t\t\tif value >= minimum && value <= amt {\n\t\t\t\tbreakpoints = append(breakpoints, breakpoint{\n\t\t\t\t\tvalue: value,\n\t\t\t\t\tscore: float64(value) * 1.5,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tavailable := belief.estimate\n\t\tif belief.lowerOK > available {\n\t\t\tavailable = belief.lowerOK\n\t\t}\n\t\tif available > edge.capacity {\n\t\t\tavailable = edge.capacity\n\t\t}\n\t\tavailable -= r.reserved[key]\n\t\tif available >= minimum && available <= amt {\n\t\t\tbreakpoints = append(breakpoints, breakpoint{\n\t\t\t\tvalue: available,\n\t\t\t\tscore: float64(available),\n\t\t\t})\n\t\t}\n\t}\n\n\tsort.Slice(breakpoints, func(i, j int) bool {\n\t\treturn breakpoints[i].score > breakpoints[j].score\n\t})\n\tif len(breakpoints) > 10 {\n\t\tbreakpoints = breakpoints[:10]\n\t}\n\tfor _, point := range breakpoints {\n\t\taddCandidate(\n\t\t\t&values, seen, point.value, minimum, amt,\n\t\t)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxRouteAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tpartCount := float64(\n\t\t\t(amt + shard - 1) / shard,\n\t\t)\n\t\tif partCount > float64(partsLeft) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Reliability remains the primary objective. The mild part-count\n\t\t// term keeps reliable unequal shards preferable to repeated probing,\n\t\t// while still allowing a full payment when its probability is good.\n\t\troutePenalty := 0.34 * math.Log1p(\n\t\t\tfloat64(r.routeFailures[choice.signature]),\n\t\t)\n\t\tchoice.utility = -math.Log(choice.probability) +\n\t\t\t0.16*math.Log(partCount) +\n\t\t\tfloat64(choice.fee)/2_500_000 +\n\t\t\troutePenalty\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tedge := r.edges[key]\n\tif edge != nil {\n\t\tbelief = normalizeBelief(belief, edge.capacity)\n\t}\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) observePass(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\n\toptimistic := edge.capacity * 4 / 5\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= belief.lowerOK {\n\t\tbelief.upperFail = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tdepletedEstimate := amt / 5\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = depletedEstimate\n\t} else {\n\t\tbelief.estimate =\n\t\t\t(3*belief.estimate + depletedEstimate) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = belief.upperFail - 1\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from != r.source {\n\t\tbelief := r.beliefs[key]\n\t\tpreEstimate := maxMSat(belief.estimate, amt)\n\t\toptimistic := edge.capacity * 9 / 10\n\t\tif preEstimate < optimistic {\n\t\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t\t}\n\n\t\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\t\tbelief.lowerOK = subtractFloor(\n\t\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t\t)\n\t\tif belief.upperFail > 0 {\n\t\t\tbelief.upperFail = subtractFloor(\n\t\t\t\tbelief.upperFail, amt, 1,\n\t\t\t)\n\t\t}\n\t\tbelief.samples++\n\t\tr.storeBelief(key, belief)\n\t}\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) releaseReservations(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[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 || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservations(keys, amounts)\n\tsignature := routeSignature(keys)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tdelete(r.routeFailures, signature)\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tr.routeFailures[signature]++\n\tr.lastFailedAmt =\n\t\trt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tfailIndex := failureEdgeIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\t// An unattributed failure says nothing reliable about a specific\n\t\t// channel. Penalize only this complete route so alternatives are\n\t\t// explored without poisoning every edge it contains.\n\t\treturn nil\n\t}\n\n\t// Every channel before the failing node forwarded the HTLC. Record that\n\t// amount as passable without applying settlement balance changes.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.observePass(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.edgeFailures[key]++\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\t\tr.edgeFailures[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 12,
"parent": 3,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxRouteAttempts = 48\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype attemptedRoute struct {\n\tpathHash uint64\n\tamount lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tpolicyBroken map[edgeKey]bool\n\tattempted map[attemptedRoute]uint32\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tpolicyBroken: make(map[edgeKey]bool),\n\t\tattempted: make(map[attemptedRoute]uint32),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\n\tcase p > 0.985:\n\t\treturn 0.985\n\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\tconfidence := math.Min(0.82, 0.20*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\nfunc (r *candidateRouter) edgeAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\tif r.policyBroken[edge.key] || !edge.usable(amt) {\n\t\treturn false\n\t}\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.key.from == r.source {\n\t\treturn total <= r.localBalances[edge.key.chanID]\n\t}\n\n\tupper := r.beliefs[edge.key].upperFail\n\treturn upper == 0 || total < upper\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tpathHash uint64\n}\n\nfunc hashPath(keys []edgeKey) uint64 {\n\thash := uint64(1469598103934665603)\n\tfor _, key := range keys {\n\t\thash ^= key.chanID\n\t\thash *= 1099511628211\n\n\t\tfor _, b := range key.from {\n\t\t\thash ^= uint64(b)\n\t\t\thash *= 1099511628211\n\t\t}\n\t\tfor _, b := range key.to {\n\t\t\thash ^= uint64(b)\n\t\t\thash *= 1099511628211\n\t\t}\n\t}\n\n\treturn hash\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\tdiversity map[edgeKey]float64) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.edgeAvailable(edge, item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tfailurePenalty := 0.24 *\n\t\t\t\tmath.Log1p(float64(r.edgeFailures[edge.key]))\n\t\t\tcapacityRatio := float64(item.amt) /\n\t\t\t\tfloat64(edge.capacity)\n\t\t\tcapacityPenalty := 0.035 * capacityRatio *\n\t\t\t\tcapacityRatio\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 + failurePenalty + capacityPenalty +\n\t\t\t\tdiversity[edge.key]\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(\n\t\t\tr.edges[key], amounts[i],\n\t\t)\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t\tpathHash: hashPath(keys),\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeAlternatives(\n\tdeliver lnwire.MilliSatoshi) []*routeChoice {\n\n\tconst alternatives = 5\n\n\tdiversity := make(map[edgeKey]float64)\n\tseen := make(map[uint64]bool)\n\tchoices := make([]*routeChoice, 0, alternatives)\n\n\tfor round := 0; round < alternatives*2; round++ {\n\t\tchoice, err := r.findRoute(deliver, diversity)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif !seen[choice.pathHash] {\n\t\t\tseen[choice.pathHash] = true\n\t\t\tchoices = append(choices, choice)\n\t\t\tif len(choices) == alternatives {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tpenalty := 0.30\n\t\tif seen[choice.pathHash] {\n\t\t\tpenalty = 0.48\n\t\t}\n\t\tfor _, key := range choice.keys {\n\t\t\tdiversity[key] += penalty\n\t\t}\n\t}\n\n\treturn choices\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\n\t\t\t\t\"route contains a cycle\",\n\t\t\t)\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 24)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt*3/5, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt*2/5, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*5/2, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*5/8,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/8,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.policyBroken[key] || belief.upperFail <= 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, numerator := range []lnwire.MilliSatoshi{7, 3, 2} {\n\t\t\tdenominator := lnwire.MilliSatoshi(8)\n\t\t\tif numerator == 3 {\n\t\t\t\tdenominator = 4\n\t\t\t}\n\t\t\tif numerator == 2 {\n\t\t\t\tdenominator = 3\n\t\t\t}\n\n\t\t\tbound := belief.upperFail * numerator / denominator\n\t\t\tif bound >= minimum && bound <= amt {\n\t\t\t\tbounds = append(bounds, bound)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 8 {\n\t\tbounds = bounds[:8]\n\t}\n\n\tfor _, bound := range bounds {\n\t\taddCandidate(\n\t\t\t&values, seen, bound, minimum, amt,\n\t\t)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc ceilParts(total, shard lnwire.MilliSatoshi) uint32 {\n\treturn uint32((total + shard - 1) / shard)\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxRouteAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tpartsNeeded := ceilParts(amt, shard)\n\t\tif partsNeeded > partsLeft {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, choice := range r.routeAlternatives(shard) {\n\t\t\tattempt := attemptedRoute{\n\t\t\t\tpathHash: choice.pathHash,\n\t\t\t\tamount: choice.deliver,\n\t\t\t}\n\t\t\ttries := r.attempted[attempt]\n\t\t\tif tries >= 1 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trisk := -math.Log(choice.probability)\n\t\t\tpartFactor := math.Sqrt(float64(partsNeeded))\n\t\t\tchoice.utility = risk*partFactor +\n\t\t\t\t0.075*float64(partsNeeded-1) +\n\t\t\t\tfloat64(choice.fee)/2_500_000 +\n\t\t\t\t0.20*float64(tries)\n\n\t\t\tif best == nil || choice.utility < best.utility {\n\t\t\t\tbest = choice\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no untried route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\n\tr.attempted[attemptedRoute{\n\t\tpathHash: best.pathHash,\n\t\tamount: best.deliver,\n\t}]++\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\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\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(key edgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnPassed(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= belief.lowerOK {\n\t\tbelief.upperFail = 0\n\t}\n\tbelief.samples++\n\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif key.from == r.source {\n\t\tbalance := r.localBalances[key.chanID]\n\t\tif amt <= balance {\n\t\t\tr.localBalances[key.chanID] = amt - 1\n\t\t}\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tfailedEstimate := amt / 4\n\tswitch {\n\tcase belief.estimate == 0:\n\t\tbelief.estimate = failedEstimate\n\n\tcase belief.estimate >= amt:\n\t\tbelief.estimate = (\n\t\t\tbelief.estimate + 2*failedEstimate\n\t\t) / 3\n\n\tdefault:\n\t\tbelief.estimate = (\n\t\t\t2*belief.estimate + failedEstimate\n\t\t) / 3\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\tbelief.samples++\n\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tbelief.samples++\n\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseReservations(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tfailureSource route.Vertex) int {\n\n\tif failureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == failureSource {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservations(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := failureEdgeIndex(rt, result.FailureSource)\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnPassed(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBroken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 13,
"parent": 3,
"score": 0.9088,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, 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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := math.Max(float64(edge.capacity)*0.10, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.78, 0.22*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.amt + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_000_000 +\n\t\t\t\t0.012 +\n\t\t\t\t0.22*float64(r.edgeFailures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(\n\t\t\tr.edges[key], amounts[i],\n\t\t)\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\n\t\t\t\t\"route contains a cycle\",\n\t\t\t)\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 18)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*4/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK >= minimum && belief.lowerOK <= amt {\n\t\t\tbounds = append(bounds, belief.lowerOK)\n\t\t}\n\t\tif belief.upperFail > 1 {\n\t\t\tbounds = append(bounds, belief.upperFail*4/5)\n\t\t\tbounds = append(bounds, belief.upperFail*2/3)\n\t\t}\n\t\tif belief.samples > 0 && belief.estimate > 0 {\n\t\t\tbounds = append(bounds, belief.estimate*4/5)\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 6 {\n\t\tbounds = bounds[:6]\n\t}\n\n\tfor _, bound := range bounds {\n\t\taddCandidate(\n\t\t\t&values, seen, bound, minimum, amt,\n\t\t)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc (r *candidateRouter) reserveChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\tr.reserved[key] += choice.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) releaseChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\tvalue := r.reserved[key]\n\t\tif value <= choice.amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = value - choice.amounts[i]\n\t\t}\n\t}\n}\n\nfunc choiceCost(choice *routeChoice) float64 {\n\treturn -math.Log(choice.probability) +\n\t\tfloat64(choice.fee)/2_000_000\n}\n\nfunc (r *candidateRouter) evaluatePlan(first *routeChoice,\n\ttotal lnwire.MilliSatoshi, partsLeft uint32) (float64, bool) {\n\n\tpartCount := uint32(\n\t\t(total + first.deliver - 1) / first.deliver,\n\t)\n\tif partCount == 0 {\n\t\tpartCount = 1\n\t}\n\tif partCount > partsLeft {\n\t\treturn 0, false\n\t}\n\n\tchoices := []*routeChoice{first}\n\tr.reserveChoice(first)\n\n\tdefer func() {\n\t\tfor i := len(choices) - 1; i >= 0; i-- {\n\t\t\tr.releaseChoice(choices[i])\n\t\t}\n\t}()\n\n\tscore := choiceCost(first)\n\tremaining := total - first.deliver\n\n\tfor slots := partCount - 1; slots > 0; slots-- {\n\t\tshard := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\n\t\tchoices = append(choices, choice)\n\t\tr.reserveChoice(choice)\n\t\tscore += choiceCost(choice)\n\t\tremaining -= shard\n\t}\n\n\tif remaining != 0 {\n\t\treturn 0, false\n\t}\n\n\t// This prices the expected reliability of the complete route set.\n\t// The small per-part charge avoids unnecessary fragmentation when\n\t// similarly reliable plans exist.\n\tscore += 0.065 * float64(partCount)\n\n\treturn score, true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tutility, ok := r.evaluatePlan(\n\t\t\tchoice, amt, partsLeft,\n\t\t)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tchoice.utility = utility\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tr.reserveChoice(best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc normalizeBelief(edge *candidateEdge,\n\tbelief *liquidityBelief) {\n\n\tif belief.estimate < 0 {\n\t\tbelief.estimate = 0\n\t}\n\tif belief.estimate > edge.capacity {\n\t\tbelief.estimate = edge.capacity\n\t}\n\tif belief.lowerOK > edge.capacity {\n\t\tbelief.lowerOK = edge.capacity\n\t}\n\tif belief.upperFail > edge.capacity {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.upperFail > 0 &&\n\t\tbelief.lowerOK >= belief.upperFail {\n\n\t\tbelief.upperFail = 0\n\t}\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tedge := r.edges[key]\n\tif edge != nil {\n\t\tnormalizeBelief(edge, &belief)\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 3\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + amt/3) / 3\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnProbeSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\n\toptimistic := edge.capacity * 4 / 5\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate =\n\t\t\t(2*belief.estimate + optimistic) / 3\n\t}\n\n\tif belief.upperFail > 0 && belief.upperFail <= amt {\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\tvalue := r.reserved[key]\n\t\tif value <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = value - amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tif len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tr.lastFailedAmt =\n\t\trt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tfailIndex := failureEdgeIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tr.edgeFailures[key]++\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Every edge before the reported failure forwarded the HTLC. Since\n\t// the failed attempt unwinds, this is evidence without a balance\n\t// shift.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnProbeSuccess(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex] + r.reserved[key]\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 14,
"parent": 3,
"score": 0.9062,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxAttempts = 48\n\tmaxPlanParts = 16\n\tplanBeamWidth = 12\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"payment specification is nil\")\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[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc reservedAmount(reservations map[edgeKey]lnwire.MilliSatoshi,\n\tkey edgeKey) lnwire.MilliSatoshi {\n\n\tif reservations == nil {\n\t\treturn 0\n\t}\n\n\treturn reservations[key]\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + reservedAmount(reservations, edge.key)\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.10\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\tconfidence := math.Min(0.78, 0.22*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservedAmount(reservations, edge.key)\n\t\t\tif item.amt+reserved > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\titem.amt+reserved >\n\t\t\t\t\tr.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(\n\t\t\t\tedge, item.amt, reservations,\n\t\t\t)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tfailurePenalty := 0.20 *\n\t\t\t\tfloat64(r.edgeFailures[edge.key])\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 + failurePenalty\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(\n\t\t\tr.edges[key], amounts[i], reservations,\n\t\t)\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc cloneReservations(\n\tsource map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tclone := make(map[edgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tclone[key] = amount\n\t}\n\n\treturn clone\n}\n\nfunc reserveChoice(reservations map[edgeKey]lnwire.MilliSatoshi,\n\tchoice *routeChoice) {\n\n\tfor i, key := range choice.keys {\n\t\treservations[key] += choice.amounts[i]\n\t}\n}\n\nfunc addAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < 1_000 {\n\t\tvalue = 1_000\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) planningAmounts(\n\tremaining lnwire.MilliSatoshi, slots uint32,\n\treservations map[edgeKey]lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 20)\n\n\taddAmount(&values, seen, remaining, remaining)\n\tif slots > 1 {\n\t\tslotsMSat := lnwire.MilliSatoshi(slots)\n\t\tbalanced := (remaining + slotsMSat - 1) / slotsMSat\n\n\t\taddAmount(&values, seen, remaining*4/5, remaining)\n\t\taddAmount(&values, seen, remaining*3/4, remaining)\n\t\taddAmount(&values, seen, remaining*2/3, remaining)\n\t\taddAmount(&values, seen, remaining/2, remaining)\n\t\taddAmount(&values, seen, remaining*2/5, remaining)\n\t\taddAmount(&values, seen, remaining/3, remaining)\n\t\taddAmount(&values, seen, remaining/4, remaining)\n\t\taddAmount(&values, seen, balanced*3/2, remaining)\n\t\taddAmount(&values, seen, balanced*5/4, remaining)\n\t\taddAmount(&values, seen, balanced, remaining)\n\t}\n\n\tif r.lastFailedAmt > 0 {\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt*4/5, remaining,\n\t\t)\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt*2/3, remaining,\n\t\t)\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt/2, remaining,\n\t\t)\n\t}\n\n\tvar breakpoints []lnwire.MilliSatoshi\n\tfor key, edge := range r.edges {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\treserved := reservedAmount(reservations, key)\n\t\tif reserved >= edge.capacity {\n\t\t\tcontinue\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tavailable := edge.capacity - reserved\n\n\t\tif belief.lowerOK > reserved {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints, belief.lowerOK-reserved,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > reserved+1 {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints,\n\t\t\t\t(belief.upperFail-reserved)*3/4,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints,\n\t\t\t\t(belief.estimate-reserved)*9/10,\n\t\t\t)\n\t\t}\n\n\t\tbreakpoints = append(breakpoints, available*4/5)\n\t}\n\n\tsort.Slice(breakpoints, func(i, j int) bool {\n\t\treturn breakpoints[i] > breakpoints[j]\n\t})\n\n\tadded := 0\n\tfor _, amount := range breakpoints {\n\t\tif amount < 1_000 || amount > remaining {\n\t\t\tcontinue\n\t\t}\n\n\t\tbefore := len(values)\n\t\taddAmount(&values, seen, amount, remaining)\n\t\tif len(values) != before {\n\t\t\tadded++\n\t\t}\n\t\tif added == 8 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc (r *candidateRouter) incrementalRisk(choice *routeChoice,\n\treservations map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\trisk := 0.0\n\tfor i, key := range choice.keys {\n\t\tedge := r.edges[key]\n\t\toldAmount := reservedAmount(reservations, key)\n\n\t\toldProbability := 1.0\n\t\tif oldAmount > 0 {\n\t\t\toldProbability = r.edgeProbability(\n\t\t\t\tedge, 0, reservations,\n\t\t\t)\n\t\t}\n\n\t\tnewProbability := r.edgeProbability(\n\t\t\tedge, choice.amounts[i], reservations,\n\t\t)\n\t\tratio := newProbability / oldProbability\n\t\tif ratio > 1 {\n\t\t\tratio = 1\n\t\t}\n\t\tif ratio < 0.005 {\n\t\t\tratio = 0.005\n\t\t}\n\n\t\trisk += -math.Log(ratio)\n\t}\n\n\treturn risk\n}\n\ntype planState struct {\n\tremaining lnwire.MilliSatoshi\n\treservations map[edgeKey]lnwire.MilliSatoshi\n\tfirst *routeChoice\n\tscore float64\n\tparts uint32\n\trank float64\n}\n\nfunc (r *candidateRouter) planRouteSet(\n\tamount lnwire.MilliSatoshi, partsLeft uint32) *routeChoice {\n\n\tif partsLeft == 0 {\n\t\treturn nil\n\t}\n\tif partsLeft > maxPlanParts {\n\t\tpartsLeft = maxPlanParts\n\t}\n\n\tstates := []*planState{{\n\t\tremaining: amount,\n\t\treservations: cloneReservations(r.reserved),\n\t}}\n\n\tvar best *planState\n\n\tfor depth := uint32(0); depth < partsLeft; depth++ {\n\t\tnextStates := make([]*planState, 0, planBeamWidth*8)\n\n\t\tfor _, state := range states {\n\t\t\tif state.remaining == 0 {\n\t\t\t\tif best == nil || state.score < best.score {\n\t\t\t\t\tbest = state\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tslots := partsLeft - depth\n\t\t\tfor _, shard := range r.planningAmounts(\n\t\t\t\tstate.remaining, slots, state.reservations,\n\t\t\t) {\n\t\t\t\tif shard < state.remaining && slots == 1 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tchoice, err := r.findRoute(\n\t\t\t\t\tshard, state.reservations,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treservations := cloneReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tstepRisk := r.incrementalRisk(\n\t\t\t\t\tchoice, state.reservations,\n\t\t\t\t)\n\t\t\t\treserveChoice(reservations, choice)\n\n\t\t\t\tfirst := state.first\n\t\t\t\tif first == nil {\n\t\t\t\t\tfirst = choice\n\t\t\t\t}\n\n\t\t\t\tremaining := state.remaining - shard\n\t\t\t\tscore := state.score + stepRisk +\n\t\t\t\t\tfloat64(choice.fee)/3_000_000 +\n\t\t\t\t\t0.025\n\n\t\t\t\tchild := &planState{\n\t\t\t\t\tremaining: remaining,\n\t\t\t\t\treservations: reservations,\n\t\t\t\t\tfirst: first,\n\t\t\t\t\tscore: score,\n\t\t\t\t\tparts: state.parts + 1,\n\t\t\t\t}\n\t\t\t\tchild.rank = child.score +\n\t\t\t\t\tfloat64(child.remaining)/\n\t\t\t\t\t\tfloat64(amount)\n\n\t\t\t\tif remaining == 0 {\n\t\t\t\t\tif best == nil || score < best.score {\n\t\t\t\t\t\tbest = child\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tnextStates = append(nextStates, child)\n\t\t\t}\n\t\t}\n\n\t\tif len(nextStates) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tsort.Slice(nextStates, func(i, j int) bool {\n\t\t\tif nextStates[i].rank == nextStates[j].rank {\n\t\t\t\treturn nextStates[i].remaining <\n\t\t\t\t\tnextStates[j].remaining\n\t\t\t}\n\n\t\t\treturn nextStates[i].rank < nextStates[j].rank\n\t\t})\n\n\t\tif len(nextStates) > planBeamWidth {\n\t\t\tnextStates = nextStates[:planBeamWidth]\n\t\t}\n\t\tstates = nextStates\n\t}\n\n\tif best != nil {\n\t\treturn best.first\n\t}\n\n\treturn nil\n}\n\nfunc (r *candidateRouter) fallbackRoute(\n\tamount lnwire.MilliSatoshi,\n\tpartsLeft uint32) *routeChoice {\n\n\tvar best *routeChoice\n\tbestScore := math.Inf(1)\n\n\tfor _, shard := range r.planningAmounts(\n\t\tamount, partsLeft, r.reserved,\n\t) {\n\t\tchoice, err := r.findRoute(shard, r.reserved)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tparts := math.Ceil(\n\t\t\tfloat64(amount) / float64(shard),\n\t\t)\n\t\tscore := r.incrementalRisk(choice, r.reserved) +\n\t\t\t0.08*parts +\n\t\t\tfloat64(choice.fee)/2_500_000\n\n\t\tif score < bestScore {\n\t\t\tbest = choice\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\treturn best\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tbest := r.planRouteSet(amt, partsLeft)\n\tif best == nil {\n\t\tbest = r.fallbackRoute(amt, partsLeft)\n\t}\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treserveChoice(r.reserved, best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 3\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + amt/3) / 3\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnProbeSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= belief.lowerOK {\n\t\tbelief.upperFail = 0\n\t}\n\n\toptimistic := edge.capacity * 9 / 10\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\tif belief.estimate > edge.capacity {\n\t\tbelief.estimate = edge.capacity\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseReservations(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tfailureSource route.Vertex) int {\n\n\tif failureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == failureSource {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservations(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.lastFailedAmt = delivered\n\n\tfailIndex := failureEdgeIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tr.edgeFailures[key]++\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Every edge before the failing edge forwarded the HTLC. This proves\n\t// liquidity for the attempted amount, but the failed HTLC rolled back,\n\t// so these observations must not shift channel balances.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnProbeSuccess(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.edgeFailures[key] += 3\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 15,
"parent": 1,
"score": 0.7381,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\n\t// Reliability is the primary routing objective. Fees remain relevant,\n\t// but should not make a cheap, low-probability route dominate a much\n\t// more reliable route.\n\triskCostMsat = 300000.0\n\n\t// Candidate shard sizes form a geometric ladder. Route selection then\n\t// jointly chooses the shard and path with the greatest expected\n\t// progress.\n\tshardStep = 0.72\n\n\tmaxShardCandidates = 18\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(\n\tamt lnwire.MilliSatoshi) bool {\n\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\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype amountPenalty struct {\n\tthreshold lnwire.MilliSatoshi\n\tcost float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tpenalties map[candidateEdgeKey]amountPenalty\n\n\tinitialShard lnwire.MilliSatoshi\n\tlastFailed lnwire.MilliSatoshi\n\tlastRemain lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tpenalties: make(map[candidateEdgeKey]amountPenalty),\n\t\tlastRemain: spec.Amount,\n\t}\n\n\tparts := spec.MaxParts\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.initialShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\n\t)\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{\n\t\tsource: true,\n\t}\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\n\treturn p\n}\n\nfunc bimodalPrior(\n\tamt, capacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\t// The first mode describes tiny payments that can pass even when the\n\t// directed side is nearly depleted. The second describes channels\n\t// whose balance is concentrated on the usable side.\n\tlowMode := 0.50 * math.Exp(-x/0.024)\n\thighMode := 0.48 / (1 + math.Exp(15*(x-0.77)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) availableLocal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[edge.key.chanID]\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.availableLocal(edge) {\n\t\t\treturn minProbability\n\t\t}\n\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.065, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\t// Bounds carry the strongest evidence. Between them, the estimate is\n\t// useful but the bimodal prior remains dominant enough to prevent a\n\t// single observation from overfitting the route choice.\n\treturn clampProbability(0.60*prior + 0.40*point)\n}\n\nfunc (r *candidateRouter) edgePenalty(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) float64 {\n\n\tpenalty, ok := r.penalties[key]\n\tif !ok || penalty.cost <= 0 {\n\t\treturn 0\n\t}\n\n\tif penalty.threshold <= 0 || amt >= penalty.threshold {\n\t\treturn penalty.cost\n\t}\n\n\t// A liquidity failure is evidence against the attempted amount, not\n\t// against every smaller shard. Penalty strength falls quadratically\n\t// below the failed amount.\n\tratio := float64(amt) / float64(penalty.threshold)\n\treturn penalty.cost * ratio * ratio * 0.20\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.availableLocal(edge) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t} else if sending > r.availableLocal(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.edgePenalty(edge.key, amtOver) + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(pq, &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})\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\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(\n\trt *route.Route, index int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(\n\trt *route.Route) lnwire.MilliSatoshi {\n\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tdelivered := deliveredAmount(rt)\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeProbability(\n\trt *route.Route) float64 {\n\n\tprobability := 1.0\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tif edge == nil {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tprobability *= r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t}\n\n\treturn clampProbability(probability)\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserved[key] += routeAmount(rt, i)\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc addShardCandidate(candidates map[lnwire.MilliSatoshi]struct{},\n\tvalue, remaining lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\treturn\n\t}\n\tif value > remaining {\n\t\tvalue = remaining\n\t}\n\n\tminimum := minShardMsat\n\tif remaining < minimum {\n\t\tminimum = remaining\n\t}\n\tif value < minimum {\n\t\treturn\n\t}\n\n\tcandidates[value] = struct{}{}\n}\n\nfunc (r *candidateRouter) shardCandidates(\n\tremaining lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tcandidates := make(map[lnwire.MilliSatoshi]struct{})\n\taddShardCandidate(candidates, remaining, remaining)\n\taddShardCandidate(candidates, r.initialShard, remaining)\n\n\tif r.lastFailed > 0 {\n\t\taddShardCandidate(\n\t\t\tcandidates,\n\t\t\tlnwire.MilliSatoshi(\n\t\t\t\tfloat64(r.lastFailed)*shardStep,\n\t\t\t),\n\t\t\tremaining,\n\t\t)\n\t\taddShardCandidate(\n\t\t\tcandidates,\n\t\t\tr.lastFailed/2,\n\t\t\tremaining,\n\t\t)\n\t}\n\n\t// A compact geometric ladder covers both large single-path attempts\n\t// and deliberately unequal MPP shards. Each amount is evaluated with\n\t// its own best path, making the choice a joint amount-route decision.\n\tvalue := remaining\n\tfor i := 0; i < maxShardCandidates; i++ {\n\t\taddShardCandidate(candidates, value, remaining)\n\n\t\tnext := lnwire.MilliSatoshi(\n\t\t\tfloat64(value) * shardStep,\n\t\t)\n\t\tif next >= value {\n\t\t\tnext = value - 1\n\t\t}\n\t\tif next < minShardMsat {\n\t\t\tbreak\n\t\t}\n\t\tvalue = next\n\t}\n\n\t// Evidence-derived boundaries are particularly valuable after a\n\t// failure because they probe just below a channel's known cliff.\n\tfor _, belief := range r.beliefs {\n\t\tif belief.upperBad != 0 {\n\t\t\taddShardCandidate(\n\t\t\t\tcandidates,\n\t\t\t\tlnwire.MilliSatoshi(\n\t\t\t\t\tfloat64(belief.upperBad)*0.67,\n\t\t\t\t),\n\t\t\t\tremaining,\n\t\t\t)\n\t\t}\n\t\tif belief.lowerOK != 0 {\n\t\t\taddShardCandidate(\n\t\t\t\tcandidates, belief.lowerOK, remaining,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate != 0 {\n\t\t\taddShardCandidate(\n\t\t\t\tcandidates,\n\t\t\t\tlnwire.MilliSatoshi(\n\t\t\t\t\tfloat64(belief.estimate)*0.82,\n\t\t\t\t),\n\t\t\t\tremaining,\n\t\t\t)\n\t\t}\n\t}\n\n\tvalues := make([]lnwire.MilliSatoshi, 0, len(candidates))\n\tfor candidate := range candidates {\n\t\tvalues = append(values, candidate)\n\t}\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\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 is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts &&\n\t\tr.spec.MaxParts != 0 {\n\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemain {\n\t\tr.lastFailed = 0\n\t}\n\tr.lastRemain = amt\n\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestScore = -1.0\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range r.shardCandidates(amt) {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := r.routeProbability(rt)\n\t\tfee := float64(routeFee(rt))\n\n\t\t// Expected progress rewards large shards, while the exponent makes\n\t\t// reliability more important than raw size. A modest fee term\n\t\t// breaks ties without steering traffic toward fragile cheap paths.\n\t\tprogress := float64(shard) *\n\t\t\tmath.Pow(probability, 1.35)\n\t\tfeeCost := fee * 25\n\t\tscore := progress - feeCost\n\n\t\tif score > bestScore {\n\t\t\tbestScore = score\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\t// In a bimodal channel, passing a meaningful amount is evidence that\n\t// the directed balance lies in the high mode.\n\testimate := amt\n\tremaining := edge.capacity - amt\n\tif remaining > 0 {\n\t\testimate += remaining * 3 / 4\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\t// A liquidity miss in a bimodal channel strongly favors the depleted\n\t// mode, while still permitting useful retries below the failed amount.\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(\n\trt *route.Route, source route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) addPenalty(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi,\n\tcost float64) {\n\n\tcurrent := r.penalties[key]\n\n\tif current.threshold == 0 ||\n\t\tamt < current.threshold {\n\n\t\tcurrent.threshold = amt\n\t}\n\tcurrent.cost += cost\n\tif current.cost > riskCostMsat*4 {\n\t\tcurrent.cost = riskCostMsat * 4\n\t}\n\n\tr.penalties[key] = current\n}\n\nfunc (r *candidateRouter) rewardEdge(\n\tkey candidateEdgeKey) {\n\n\tpenalty, ok := r.penalties[key]\n\tif !ok {\n\t\treturn\n\t}\n\n\tpenalty.cost *= 0.20\n\tif penalty.cost < 1 {\n\t\tdelete(r.penalties, key)\n\t\treturn\n\t}\n\n\tr.penalties[key] = penalty\n}\n\nfunc (r *candidateRouter) ReportAttempt(\n\t_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.rewardEdge(key)\n\t\t}\n\n\t\tr.lastFailed = 0\n\t\treturn nil\n\t}\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered > 0 {\n\t\tr.lastFailed = delivered\n\t}\n\n\tfailIndex := r.failureIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.addPenalty(\n\t\t\t\t\tkey, routeAmount(rt, i),\n\t\t\t\t\triskCostMsat/3,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Every edge before the failing node carried the HTLC successfully.\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\tr.rewardEdge(key)\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// These failures are deterministic for the advertised policy and\n\t\t// constructed route, so retrying this directed edge is wasteful.\n\t\tr.blocked[key] = true\n\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(\n\t\t\tkey, routeAmount(rt, failIndex),\n\t\t)\n\t\tr.addPenalty(\n\t\t\tkey, routeAmount(rt, failIndex),\n\t\t\triskCostMsat,\n\t\t)\n\n\tdefault:\n\t\t// Unknown failures should encourage route exploration without\n\t\t// creating a permanent liquidity bound from ambiguous evidence.\n\t\tr.addPenalty(\n\t\t\tkey, routeAmount(rt, failIndex),\n\t\t\triskCostMsat/2,\n\t\t)\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 16,
"parent": 1,
"score": 0.5078,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 300000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.62\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedCandidateBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tfailedNow map[candidateEdgeKey]lnwire.MilliSatoshi\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tused map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastFailed lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tfailedNow: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tused: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tparts := spec.MaxParts\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\n\tswitch {\n\tcase spec.Amount < 25_000_000:\n\t\tparts = 1\n\tcase spec.Amount < 100_000_000 && parts > 2:\n\t\tparts = 2\n\tcase spec.Amount < 300_000_000 && parts > 4:\n\t\tparts = 4\n\t}\n\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\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.edgeByKey[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.\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.edgeByKey[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\tsharedCandidateBeliefs.Lock()\n\tfor key, belief := range sharedCandidateBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedCandidateBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\n\treturn (a + b - 1) / b\n}\n\nfunc subtractFloor(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b >= a {\n\t\treturn 0\n\t}\n\n\treturn a - b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.024)\n\thighMode := 0.475 / (1 + math.Exp(15*(x-0.80)))\n\n\treturn clampProbability(0.004 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) available(edge *candidateEdge) lnwire.MilliSatoshi {\n\tavailable := edge.capacity\n\tavailable = subtractFloor(available, r.used[edge.key])\n\tavailable = subtractFloor(available, r.reserved[edge.key])\n\n\tif edge.key.from == r.source {\n\t\tlocal := r.localBalances[edge.key.chanID]\n\t\tlocal = subtractFloor(local, r.used[edge.key])\n\t\tlocal = subtractFloor(local, r.reserved[edge.key])\n\t\tif local < available {\n\t\t\tavailable = local\n\t\t}\n\t}\n\n\treturn available\n}\n\nfunc (r *candidateRouter) adjustedBelief(\n\tkey candidateEdgeKey) liquidityBelief {\n\n\tbelief := r.beliefs[key]\n\tconsumed := r.used[key]\n\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, consumed)\n\tbelief.estimate = subtractFloor(belief.estimate, consumed)\n\n\tif belief.upperBad != 0 {\n\t\tif consumed >= belief.upperBad {\n\t\t\tbelief.upperBad = 1\n\t\t} else {\n\t\t\tbelief.upperBad -= consumed\n\t\t}\n\t}\n\n\treturn belief\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tavailable := r.available(edge)\n\tif amt > available {\n\t\treturn minProbability\n\t}\n\tif edge.key.from == r.source {\n\t\treturn maxProbability\n\t}\n\n\tif upper := r.failedNow[edge.key]; upper != 0 && amt >= upper {\n\t\treturn minProbability\n\t}\n\n\tbelief := r.adjustedBelief(edge.key)\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, available)\n\tif belief.estimate == 0 && belief.upperBad == 0 {\n\t\treturn prior\n\t}\n\n\testimate := belief.estimate\n\tif estimate == 0 && belief.upperBad != 0 {\n\t\testimate = belief.upperBad * 35 / 100\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.06, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(estimate))/width,\n\t))\n\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tpoint *= 0.08\n\t}\n\n\treturn clampProbability(0.48*prior + 0.52*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\tamtOver > r.available(edge) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif upper := r.failedNow[edge.key]; upper != 0 &&\n\t\t\t\tamtOver >= upper {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t} else if sending > r.available(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\n\t\t\tutilization := float64(amtOver) /\n\t\t\t\tmath.Max(float64(r.available(edge)), 1)\n\t\t\tcapacityCost := utilization * utilization * 20_000\n\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tcapacityCost + r.routePenalty[edge.key] + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(pq, &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})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendShardCandidate(values []lnwire.MilliSatoshi,\n\tvalue, floor, ceiling lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < floor {\n\t\tvalue = floor\n\t}\n\tif value > ceiling {\n\t\tvalue = ceiling\n\t}\n\tif value <= 0 {\n\t\treturn values\n\t}\n\n\tfor _, existing := range values {\n\t\tdiff := existing - value\n\t\tif diff < 0 {\n\t\t\tdiff = -diff\n\t\t}\n\t\tif diff <= 1 {\n\t\t\treturn values\n\t\t}\n\t}\n\n\treturn append(values, value)\n}\n\nfunc (r *candidateRouter) shardCandidates(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tfloor := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft)*2,\n\t)\n\tif floor < minShardMsat {\n\t\tfloor = minShardMsat\n\t}\n\tif floor > amt {\n\t\tfloor = amt\n\t}\n\n\ttarget := r.baseShard\n\tif target <= 0 || target > amt {\n\t\ttarget = ceilDiv(amt, lnwire.MilliSatoshi(partsLeft))\n\t}\n\n\tvar values []lnwire.MilliSatoshi\n\tvalues = appendShardCandidate(values, target, floor, amt)\n\n\tif r.lastFailed != 0 {\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(r.lastFailed) * lowerRetryFactor,\n\t\t)\n\t\tvalues = appendShardCandidate(values, retry, floor, amt)\n\t}\n\n\tequal := ceilDiv(amt, lnwire.MilliSatoshi(partsLeft))\n\tvalues = appendShardCandidate(values, equal, floor, amt)\n\n\tfor _, factor := range []float64{0.82, 0.68, 0.54} {\n\t\tnext := lnwire.MilliSatoshi(float64(target) * factor)\n\t\tvalues = appendShardCandidate(values, next, floor, amt)\n\t}\n\n\tvalues = appendShardCandidate(values, floor, floor, amt)\n\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tcandidates := r.shardCandidates(amt, partsLeft)\n\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestScore = math.Inf(1)\n\t\tlastErr error\n\t)\n\n\tfloor := candidates[len(candidates)-1]\n\tfor _, shard := range candidates {\n\t\trt, score, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tsizeCredit := 35_000 * math.Log1p(\n\t\t\tfloat64(shard) / math.Max(float64(floor), 1),\n\t\t)\n\t\tadjusted := score - sizeCredit\n\n\t\tif adjusted < bestScore {\n\t\t\tbestScore = adjusted\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedCandidateBeliefs.Lock()\n\tsharedCandidateBeliefs.values[key] = belief\n\tsharedCandidateBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tremaining := subtractFloor(edge.capacity, amt)\n\testimate += remaining * 4 / 5\n\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) consumeBelief(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tbelief.estimate = subtractFloor(belief.estimate, amt)\n\n\tif belief.upperBad != 0 {\n\t\tif amt >= belief.upperBad {\n\t\t\tbelief.upperBad = 1\n\t\t} else {\n\t\t\tbelief.upperBad -= amt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.failedNow[key] = amt\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamt := routeAmount(rt, i)\n\t\t\tr.recordSuccess(key, amt)\n\t\t\tr.routePenalty[key] *= 0.15\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamt := routeAmount(rt, i)\n\t\t\tr.consumeBelief(key, amt)\n\t\t\tr.used[key] += amt\n\t\t}\n\n\t\tr.lastFailed = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.35\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailed = deliveredAmount(rt)\n\t\tif r.lastFailed > 0 {\n\t\t\tr.baseShard = lnwire.MilliSatoshi(\n\t\t\t\tfloat64(r.lastFailed) * lowerRetryFactor,\n\t\t\t)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.5\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmt := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmt)\n\t\tr.routePenalty[key] += riskCostMsat\n\n\t\tr.lastFailed = deliveredAmount(rt)\n\t\tif r.lastFailed > 0 {\n\t\t\tretry := lnwire.MilliSatoshi(\n\t\t\t\tfloat64(r.lastFailed) * lowerRetryFactor,\n\t\t\t)\n\t\t\tif retry > 0 {\n\t\t\t\tr.baseShard = retry\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 17,
"parent": 1,
"score": 0.7688,
"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\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 260000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.64\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n\tmaxPlanCandidates = 18\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := spec.MaxParts\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(spec.Amount, lnwire.MilliSatoshi(parts))\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.026)\n\thighMode := 0.475 / (1 + math.Exp(15*(x-0.79)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) available(edge *candidateEdge) lnwire.MilliSatoshi {\n\tavailable := edge.capacity\n\tif edge.key.from == r.source {\n\t\tavailable = r.localBalances[edge.key.chanID]\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif amt > r.available(edge) {\n\t\treturn minProbability\n\t}\n\tif edge.key.from == r.source {\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.52*prior + 0.48*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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) || amtOver > r.available(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif probability <= minProbability &&\n\t\t\t\tbelief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tif sending > r.available(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\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\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) routeProbability(rt *route.Route) float64 {\n\tprobability := 1.0\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability *= r.probability(edge, routeAmount(rt, i))\n\t}\n\n\treturn clampProbability(probability)\n}\n\nfunc addCandidate(values map[lnwire.MilliSatoshi]struct{},\n\tvalue, floor, ceiling lnwire.MilliSatoshi) {\n\n\tif value < floor || value > ceiling || value <= 0 {\n\t\treturn\n\t}\n\tvalues[value] = struct{}{}\n}\n\nfunc (r *candidateRouter) shardCandidates(amt,\n\tequal lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tfloor := equal / 3\n\tif floor < minShardMsat {\n\t\tfloor = minShardMsat\n\t}\n\tif floor > amt {\n\t\tfloor = amt\n\t}\n\n\tvalues := make(map[lnwire.MilliSatoshi]struct{})\n\taddCandidate(values, amt, floor, amt)\n\taddCandidate(values, equal, floor, amt)\n\taddCandidate(values, r.baseShard, floor, amt)\n\taddCandidate(values, equal*2, floor, amt)\n\taddCandidate(values, equal*3/2, floor, amt)\n\taddCandidate(values, equal*4/3, floor, amt)\n\taddCandidate(values, equal*5/4, floor, amt)\n\taddCandidate(values, equal*3/4, floor, amt)\n\taddCandidate(values, equal*2/3, floor, amt)\n\taddCandidate(values, equal/2, floor, amt)\n\taddCandidate(values, amt*4/5, floor, amt)\n\taddCandidate(values, amt*2/3, floor, amt)\n\taddCandidate(values, amt/2, floor, amt)\n\taddCandidate(values, amt/3, floor, amt)\n\taddCandidate(values, floor, floor, amt)\n\n\tvar bestOK lnwire.MilliSatoshi\n\tvar bestRetry lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.blocked[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK >= floor && belief.lowerOK <= amt &&\n\t\t\tbelief.lowerOK > bestOK {\n\n\t\t\tbestOK = belief.lowerOK\n\t\t}\n\n\t\tif belief.upperBad != 0 {\n\t\t\tretry := belief.upperBad * 62 / 100\n\t\t\tif retry >= floor && retry <= amt && retry > bestRetry {\n\t\t\t\tbestRetry = retry\n\t\t\t}\n\t\t}\n\t}\n\n\taddCandidate(values, bestOK, floor, amt)\n\taddCandidate(values, bestRetry, floor, amt)\n\n\tcandidates := make([]lnwire.MilliSatoshi, 0, len(values))\n\tfor value := range values {\n\t\tcandidates = append(candidates, value)\n\t}\n\tsort.Slice(candidates, func(i, j int) bool {\n\t\treturn candidates[i] > candidates[j]\n\t})\n\n\tif len(candidates) <= maxPlanCandidates {\n\t\treturn candidates\n\t}\n\n\ttrimmed := append(\n\t\t[]lnwire.MilliSatoshi(nil),\n\t\tcandidates[:maxPlanCandidates-2]...,\n\t)\n\ttrimmed = append(\n\t\ttrimmed,\n\t\tcandidates[len(candidates)-2],\n\t\tcandidates[len(candidates)-1],\n\t)\n\n\treturn trimmed\n}\n\nfunc (r *candidateRouter) planRoute(amt,\n\tequal lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tcandidates := r.shardCandidates(amt, equal)\n\n\tvar bestRoute *route.Route\n\tbestUtility := math.Inf(-1)\n\tvar lastErr error\n\n\tfor _, shard := range candidates {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := r.routeProbability(rt)\n\t\tfees := rt.TotalAmount - deliveredAmount(rt)\n\t\tprogress := math.Log(\n\t\t\tmath.Max(float64(shard)/float64(equal), 0.05),\n\t\t)\n\n\t\tutility := math.Log(probability) + 0.48*progress -\n\t\t\tfloat64(fees)/750000.0\n\n\t\tif shard < equal {\n\t\t\tutility += 0.30 * progress\n\t\t}\n\t\tif probability >= 0.70 {\n\t\t\tutility += 0.18\n\t\t}\n\t\tif shard == amt && probability >= 0.45 {\n\t\t\tutility += 0.20\n\t\t}\n\n\t\tif bestRoute == nil || utility > bestUtility {\n\t\t\tbestRoute = rt\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\treturn bestRoute, nil\n\t}\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tequal := ceilDiv(amt, lnwire.MilliSatoshi(partsLeft))\n\tif partsLeft == 1 {\n\t\tequal = amt\n\t}\n\n\trt, err := r.planRoute(amt, equal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.reserve(rt)\n\treturn rt, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt + (edge.capacity-amt)*4/5\n\tif estimate > edge.capacity {\n\t\testimate = edge.capacity\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.estimate < belief.lowerOK {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > r.baseShard {\n\t\t\tr.baseShard = delivered\n\t\t}\n\t\tr.consecutiveFailures = 0\n\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 2\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.70\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmount := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmount)\n\t\tr.routePenalty[key] += riskCostMsat\n\n\t\tretry := deliveredAmount(rt) * 62 / 100\n\t\tif retry >= minShardMsat &&\n\t\t\t(r.baseShard == 0 || retry < r.baseShard) {\n\n\t\t\tr.baseShard = retry\n\t\t}\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 3 {\n\t\tfor edgeKey, penalty := range r.routePenalty {\n\t\t\tif edgeKey == key {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.routePenalty[edgeKey] = penalty * 0.85\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 18,
"parent": 3,
"score": 0.7666,
"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\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxRouteAttempts = 72\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tpolicyFailures map[edgeKey]bool\n\trouteFailures map[string]uint32\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tpolicyFailures: make(map[edgeKey]bool),\n\t\trouteFailures: make(map[string]uint32),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := math.Max(float64(edge.capacity)*0.09, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.80, 0.20*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tsignature string\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\trequiredAtNode := deliver\n\t\tif item.node != r.spec.Target {\n\t\t\tpathEdge := next[item.node]\n\t\t\tif pathEdge == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequiredAtNode = requiredAmount(\n\t\t\t\titem.node, r.spec.Target, deliver, next,\n\t\t\t)\n\t\t\tif requiredAtNode <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.policyFailures[edge.key] ||\n\t\t\t\t!edge.usable(requiredAtNode) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := requiredAtNode + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, requiredAtNode)\n\t\t\tedgeFee := edge.fee(requiredAtNode)\n\t\t\tsending := requiredAtNode + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = requiredAtNode\n\t\t\t}\n\n\t\t\treservationRatio := float64(r.reserved[edge.key]) /\n\t\t\t\tmath.Max(float64(edge.capacity), 1)\n\t\t\tfailurePenalty := 0.55 *\n\t\t\t\tmath.Min(float64(r.edgeFailures[edge.key]), 6)\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 +\n\t\t\t\tfailurePenalty +\n\t\t\t\t0.70*reservationRatio\n\n\t\t\tcandidate := item.score + step\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t})\n\n\t\t\t_ = sending\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t\tsignature: routeSignature(keys, deliver),\n\t}, nil\n}\n\nfunc requiredAmount(node, target route.Vertex,\n\tdeliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) lnwire.MilliSatoshi {\n\n\tvar path []*candidateEdge\n\tfor node != target {\n\t\tedge := next[node]\n\t\tif edge == nil {\n\t\t\treturn 0\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(next)+1 {\n\t\t\treturn 0\n\t\t}\n\t}\n\n\tamt := deliver\n\tfor i := len(path) - 1; i > 0; i-- {\n\t\tamt += path[i].fee(amt)\n\t}\n\n\treturn amt\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamounts[last] = deliver\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc routeSignature(keys []edgeKey,\n\tdeliver lnwire.MilliSatoshi) string {\n\n\tvar builder strings.Builder\n\tfor _, key := range keys {\n\t\tbuilder.WriteString(strconv.FormatUint(key.chanID, 10))\n\t\tbuilder.WriteByte('/')\n\t}\n\tbuilder.WriteByte('@')\n\tbuilder.WriteString(strconv.FormatInt(int64(deliver), 10))\n\n\treturn builder.String()\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tpartCount := lnwire.MilliSatoshi(partsLeft)\n\tminimum := (amt + partCount - 1) / partCount\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 20)\n\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\taddCandidate(&values, seen, minimum*5/4, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2, minimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/5, minimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3, minimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4, minimum, amt,\n\t\t)\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.policyFailures[key] || belief.upperFail <= 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, factor := range []lnwire.MilliSatoshi{1, 2, 3} {\n\t\t\tbound := belief.upperFail * factor / 4\n\t\t\tif bound >= minimum && bound <= amt {\n\t\t\t\tbounds = append(bounds, bound)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 8 {\n\t\tbounds = bounds[:8]\n\t}\n\tfor _, bound := range bounds {\n\t\taddCandidate(&values, seen, bound, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] < values[j]\n\t})\n\n\treturn values\n}\n\nfunc ceilParts(total, shard lnwire.MilliSatoshi) uint32 {\n\tif shard <= 0 {\n\t\treturn math.MaxUint32\n\t}\n\n\treturn uint32((total + shard - 1) / shard)\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxRouteAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tpartsNeeded := ceilParts(amt, shard)\n\t\tif partsNeeded > partsLeft {\n\t\t\tcontinue\n\t\t}\n\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\trisk := -math.Log(choice.probability)\n\t\trepeatPenalty := 1.35 *\n\t\t\tfloat64(r.routeFailures[choice.signature])\n\t\tpartPenalty := 0.075 * float64(partsNeeded-1)\n\t\tfeePenalty := float64(choice.fee) / 2_500_000\n\n\t\t// Prefer reliable, deliberately sized shards. The low part penalty\n\t\t// lets reliability dominate while still avoiding needless splits.\n\t\tchoice.utility = risk + repeatPenalty +\n\t\t\tpartPenalty + feePenalty\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnForwarded(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\n\toptimistic := edge.capacity * 4 / 5\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= belief.lowerOK {\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tdepletedEstimate := amt / 4\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = depletedEstimate\n\t} else {\n\t\tbelief.estimate =\n\t\t\t(3*belief.estimate + depletedEstimate) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\n\t// A successful transfer is strong evidence for the liquid mode. Before\n\t// applying the transfer, infer that a substantial fraction of capacity\n\t// was available in this direction.\n\tinferredBefore := maxMSat(belief.lowerOK, amt)\n\tliquidMode := edge.capacity * 4 / 5\n\tif inferredBefore < liquidMode {\n\t\tinferredBefore = liquidMode\n\t}\n\n\testimateBefore := maxMSat(belief.estimate, inferredBefore)\n\tbelief.lowerOK = subtractFloor(inferredBefore, amt, 0)\n\tbelief.estimate = subtractFloor(estimateBefore, amt, 0)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tif belief.upperFail > 0 &&\n\t\tbelief.lowerOK >= belief.upperFail {\n\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tfailureSource route.Vertex) int {\n\n\tif failureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != failureSource {\n\t\t\tcontinue\n\t\t}\n\n\t\t// A node reports failure for its outgoing channel. The final\n\t\t// recipient has no outgoing route edge.\n\t\treturn i + 1\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) releaseReservation(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[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 || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservation(keys, amounts)\n\n\tsignature := routeSignature(\n\t\tkeys, rt.Hops[len(rt.Hops)-1].AmtToForward,\n\t)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tdelete(r.routeFailures, signature)\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tr.routeFailures[signature]++\n\tr.lastFailedAmt =\n\t\trt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tfailIndex := failureEdgeIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\treturn nil\n\t}\n\n\t// A failure returned by a downstream node proves that every preceding\n\t// channel carried the HTLC. Record that evidence without shifting\n\t// liquidity because the failed HTLC is rolled back.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnForwarded(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.edgeFailures[key]++\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.edgeFailures[key] += 2\n\t\tr.policyFailures[key] = true\n\n\tdefault:\n\t\tr.edgeFailures[key]++\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 19,
"parent": 3,
"score": 0.7764,
"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 finalCltvDelta = 40\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\tbaseFee lnwire.MilliSatoshi\n\tfeeRate lnwire.MilliSatoshi\n\tdelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFee + amt*e.feeRate/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocal map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tfailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailed lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincoming: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocal: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tfailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\tfor id, balance := range localBalances {\n\t\tr.local[id] = balance\n\t}\n\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tctx := context.Background()\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFee: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRate: policy.FeeProportionalMillionths,\n\t\t\t\t\tdelta: 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.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlow := 0.50 * math.Exp(-18*x)\n\thigh := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\treturn clampProbability(low + high)\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\n\tif edge.key.from == r.source {\n\t\tif total <= r.local[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := math.Max(1, float64(edge.capacity)*0.08)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\tconfidence := math.Min(0.82, 0.24*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype pathItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype pathQueue []*pathItem\n\nfunc (q pathQueue) Len() int { return len(q) }\nfunc (q pathQueue) Less(i, j int) bool { return q[i].score < q[j].score }\nfunc (q pathQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }\n\nfunc (q *pathQueue) Push(value any) {\n\t*q = append(*q, value.(*pathItem))\n}\n\nfunc (q *pathQueue) Pop() any {\n\told := *q\n\titem := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn item\n}\n\ntype routeChoice struct {\n\trt *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 || r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"invalid route request\")\n\t}\n\n\tscores := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tqueue := &pathQueue{}\n\theap.Push(queue, &pathItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*pathItem)\n\t\tbest, ok := scores[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incoming[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.amt + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.local[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 +\n\t\t\t\t0.42*float64(r.failures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := scores[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &pathItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.probability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\trt: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, errors.New(\"incomplete route\")\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.delta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\tif minimum > amt {\n\t\tminimum = amt\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 18)\n\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tamt,\n\t\tamt * 4 / 5,\n\t\tamt * 2 / 3,\n\t\tamt / 2,\n\t\tminimum * 2,\n\t\tminimum * 3 / 2,\n\t\tminimum,\n\t} {\n\t\taddAmount(&values, seen, value, minimum, amt)\n\t}\n\n\tif r.lastFailed > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailed * 2 / 3,\n\t\t\tr.lastFailed / 2,\n\t\t\tr.lastFailed * 2 / 5,\n\t\t\tr.lastFailed / 3,\n\t\t\tr.lastFailed / 4,\n\t\t} {\n\t\t\taddAmount(&values, seen, value, minimum, amt)\n\t\t}\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\t\tif belief.lowerOK >= minimum && belief.lowerOK <= amt {\n\t\t\tbounds = append(bounds, belief.lowerOK)\n\t\t}\n\t\tif belief.upperFail > 1 {\n\t\t\tbounds = append(\n\t\t\t\tbounds,\n\t\t\t\tbelief.upperFail*2/3,\n\t\t\t\tbelief.upperFail/2,\n\t\t\t\tbelief.upperFail/3,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 7 {\n\t\tbounds = bounds[:7]\n\t}\n\tfor _, value := range bounds {\n\t\taddAmount(&values, seen, value, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tpartsNeeded := math.Ceil(\n\t\t\tfloat64(amt) / float64(shard),\n\t\t)\n\t\tif partsNeeded > float64(partsLeft) {\n\t\t\tcontinue\n\t\t}\n\n\t\tchoice.utility =\n\t\t\t-math.Log(choice.probability)*partsNeeded +\n\t\t\t\t0.11*(partsNeeded-1) +\n\t\t\t\tfloat64(choice.fee)*partsNeeded/2_500_000 +\n\t\t\t\t0.18*math.Log(float64(shard)/float64(amt))\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.rt, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tdepleted := amt / 4\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = depleted\n\t} else {\n\t\tbelief.estimate = (3*belief.estimate + depleted) / 4\n\t}\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnProbeSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif key.from == r.source || r.edges[key] == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= amt {\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\testimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif estimate < optimistic {\n\t\testimate = (estimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(estimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"invalid attempted route\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\tif r.reserved[key] <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.local[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.local[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.local[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\t\t\tif r.failures[key] > 0 {\n\t\t\t\tr.failures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailed = 0\n\t\treturn nil\n\t}\n\n\tr.lastFailed = rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tfailIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tif r.failures[key] < 3 {\n\t\t\t\tr.failures[key]++\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnProbeSuccess(keys[i], amounts[i])\n\t\tif r.failures[keys[i]] > 0 {\n\t\t\tr.failures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tif r.failures[key] < 12 {\n\t\tr.failures[key] += 2\n\t}\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, amounts[failIndex])\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 20,
"parent": 3,
"score": 0.7682,
"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\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxAttempts = 72\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\ttried map[string]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t\ttried: make(map[string]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, 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.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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, exists := sharedBeliefs.values[key]\n\t\tif !exists {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(probability float64) float64 {\n\tswitch {\n\tcase probability < 0.005:\n\t\treturn 0.005\n\tcase probability > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn probability\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*ratio)\n\thighMode := 0.495 / (1 + math.Exp(22*(ratio-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.10\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\tconfidence := math.Min(0.80, 0.20*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\tdiversity map[edgeKey]float64) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, exists := score[item.node]\n\t\tif !exists || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.amt + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tfailurePenalty := math.Min(\n\t\t\t\t0.75,\n\t\t\t\t0.12*float64(r.edgeFailures[edge.key]),\n\t\t\t)\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 +\n\t\t\t\tfailurePenalty +\n\t\t\t\tdiversity[edge.key]\n\n\t\t\tcandidate := item.score + step\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) baseCandidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) ([]lnwire.MilliSatoshi,\n\tlnwire.MilliSatoshi) {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}, amt\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\tif minimum > amt {\n\t\tminimum = amt\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 24)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt*3/5, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt*2/5, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum*5/4, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*4/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/3,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch {\n\t\tcase belief.upperFail > 1:\n\t\t\tbounds = append(\n\t\t\t\tbounds,\n\t\t\t\tbelief.upperFail*4/5,\n\t\t\t\tbelief.upperFail*2/3,\n\t\t\t)\n\n\t\tcase belief.lowerOK > 0:\n\t\t\tbounds = append(bounds, belief.lowerOK)\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 8 {\n\t\tbounds = bounds[:8]\n\t}\n\n\tfor _, bound := range bounds {\n\t\taddCandidate(\n\t\t\t&values, seen, bound, minimum, amt,\n\t\t)\n\t}\n\n\treturn values, minimum\n}\n\nfunc (r *candidateRouter) edgePlanningLimit(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\treserved := r.reserved[edge.key]\n\tvar limit lnwire.MilliSatoshi\n\n\tif edge.key.from == r.source {\n\t\tlimit = r.localBalances[edge.key.chanID]\n\t} else {\n\t\tbelief := r.beliefs[edge.key]\n\n\t\tswitch {\n\t\tcase belief.upperFail > 1:\n\t\t\tlimit = belief.upperFail - 1\n\n\t\tcase belief.lowerOK > 0:\n\t\t\tlimit = maxMSat(\n\t\t\t\tbelief.lowerOK,\n\t\t\t\tbelief.estimate,\n\t\t\t)\n\n\t\tcase belief.samples > 0 && belief.estimate > 0:\n\t\t\tlimit = belief.estimate\n\n\t\tdefault:\n\t\t\tlimit = edge.capacity * 4 / 5\n\t\t}\n\t}\n\n\tif limit > edge.capacity {\n\t\tlimit = edge.capacity\n\t}\n\tif edge.maxHTLC > 0 && limit > edge.maxHTLC {\n\t\tlimit = edge.maxHTLC\n\t}\n\tif reserved >= limit {\n\t\treturn 0\n\t}\n\n\treturn limit - reserved\n}\n\nfunc (r *candidateRouter) pathBudget(\n\tchoice *routeChoice) lnwire.MilliSatoshi {\n\n\tif choice == nil || choice.deliver <= 0 {\n\t\treturn 0\n\t}\n\n\tbudget := float64(math.MaxInt64)\n\tproven := true\n\n\tfor i, key := range choice.keys {\n\t\tedge := r.edges[key]\n\t\tif edge == nil || choice.amounts[i] <= 0 {\n\t\t\treturn 0\n\t\t}\n\n\t\tlimit := r.edgePlanningLimit(edge)\n\t\tif limit <= 0 {\n\t\t\treturn 0\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tif key.from != r.source &&\n\t\t\t(belief.lowerOK == 0 ||\n\t\t\t\tlimit > belief.lowerOK) {\n\n\t\t\tproven = false\n\t\t}\n\n\t\tdeliverLimit := float64(choice.deliver) *\n\t\t\tfloat64(limit) / float64(choice.amounts[i])\n\t\tif deliverLimit < budget {\n\t\t\tbudget = deliverLimit\n\t\t}\n\t}\n\n\tif math.IsInf(budget, 0) || budget <= 0 {\n\t\treturn 0\n\t}\n\n\t// Unknown corridors are sized below their inferred high-liquidity\n\t// mode. Proven corridors can be filled more aggressively.\n\tif proven {\n\t\tbudget *= 0.97\n\t} else {\n\t\tbudget *= 0.82\n\t}\n\n\tif budget > float64(math.MaxInt64) {\n\t\tbudget = float64(math.MaxInt64)\n\t}\n\n\treturn lnwire.MilliSatoshi(budget)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tvalues, minimum := r.baseCandidateAmounts(amt, partsLeft)\n\tseen := make(map[lnwire.MilliSatoshi]bool, len(values)+12)\n\tfor _, value := range values {\n\t\tseen[value] = true\n\t}\n\n\t// Probe the smallest feasible shard to discover several distinct\n\t// corridors, then turn each corridor's inferred bottleneck into an\n\t// unequal shard candidate.\n\tdiversity := make(map[edgeKey]float64)\n\tfor i := 0; i < 6; i++ {\n\t\tchoice, err := r.findRoute(minimum, diversity)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tbudget := r.pathBudget(choice)\n\t\taddCandidate(\n\t\t\t&values, seen, budget, minimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, budget*4/5, minimum, amt,\n\t\t)\n\n\t\tfor _, key := range choice.keys {\n\t\t\tdiversity[key] += 0.85\n\t\t}\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\tif len(values) > 24 {\n\t\tvalues = values[:24]\n\t\tif !seen[minimum] {\n\t\t\tvalues[len(values)-1] = minimum\n\t\t}\n\t}\n\n\treturn values\n}\n\nfunc routeSignature(choice *routeChoice) string {\n\tvar builder strings.Builder\n\tbuilder.Grow(24 + len(choice.keys)*20)\n\tbuilder.WriteString(strconv.FormatInt(\n\t\tint64(choice.deliver), 10,\n\t))\n\n\tfor _, key := range choice.keys {\n\t\tbuilder.WriteByte('/')\n\t\tbuilder.WriteString(strconv.FormatUint(key.chanID, 10))\n\t}\n\n\treturn builder.String()\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\tvar repeated *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tdiversity := make(map[edgeKey]float64)\n\n\t\tfor alternative := 0; alternative < 5; alternative++ {\n\t\t\tchoice, err := r.findRoute(shard, diversity)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tneeded := math.Ceil(\n\t\t\t\tfloat64(amt) / float64(shard),\n\t\t\t)\n\t\t\tchoice.utility = -math.Log(choice.probability) +\n\t\t\t\t0.055*(needed-1) +\n\t\t\t\tfloat64(choice.fee)/2_500_000\n\n\t\t\tsignature := routeSignature(choice)\n\t\t\tif r.tried[signature] {\n\t\t\t\tchoice.utility += 4.0\n\t\t\t\tif repeated == nil ||\n\t\t\t\t\tchoice.utility < repeated.utility {\n\n\t\t\t\t\trepeated = choice\n\t\t\t\t}\n\t\t\t} else if best == nil ||\n\t\t\t\tchoice.utility < best.utility {\n\n\t\t\t\tbest = choice\n\t\t\t}\n\n\t\t\tfor _, key := range choice.keys {\n\t\t\t\tdiversity[key] += 0.95\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\tbest = repeated\n\t}\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tr.tried[routeSignature(best)] = true\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tfailedEstimate := amt / 4\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = failedEstimate\n\t} else {\n\t\tbelief.estimate =\n\t\t\t(3*belief.estimate + failedEstimate) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnProbeSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\toptimistic := edge.capacity * 9 / 10\n\tif belief.estimate < optimistic {\n\t\tif belief.estimate < amt {\n\t\t\tbelief.estimate = amt\n\t\t}\n\t\tbelief.estimate =\n\t\t\t(belief.estimate + optimistic) / 2\n\t}\n\n\tif belief.upperFail > 0 &&\n\t\tbelief.upperFail <= belief.lowerOK {\n\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\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\tr.lastFailedAmt =\n\t\trt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\treturn nil\n\t}\n\n\t// Every edge before the failure carried the HTLC successfully. Since\n\t// the failed attempt unwinds, this is evidence without a net liquidity\n\t// shift.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnProbeSuccess(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 21,
"parent": 3,
"score": 0.5077,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype 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 <= 0 || 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\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\t// paymentUpper records failures observed by this router. Unlike shared\n\t// beliefs, these bounds are fresh enough to exclude an amount outright.\n\t// A new payment receives a new router and can cheaply refresh stale shared\n\t// evidence if background traffic has moved liquidity.\n\tpaymentUpper map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpaymentUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.82, 0.20*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\nfunc (r *candidateRouter) freshlyImpossible(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\tupper := r.paymentUpper[edge.key]\n\treturn upper > 0 && amt+r.reserved[edge.key] >= upper\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif r.freshlyImpossible(edge, item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.amt + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\t// Reliability is the primary cost. Capacity enters through the\n\t\t\t// bimodal probability, while fees and hop count break close\n\t\t\t// reliability ties. Only the actually failing edge accumulates\n\t\t\t// the explicit failure penalty.\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/3_000_000 +\n\t\t\t\t0.012 +\n\t\t\t\t0.24*float64(r.edgeFailures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 20)\n\n\t// Include both balanced allocations and deliberately unequal shards.\n\t// The latter allow a large corridor to carry most of the payment while\n\t// preserving enough part slots for smaller parallel corridors.\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*4/5, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt*3/5, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt*2/5, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum*5/4, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*4/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\t// Fresh failure bounds are especially useful breakpoints. Shared bounds\n\t// are also considered, but they do not exclude routes because background\n\t// traffic may have invalidated them between payments.\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, upper := range r.paymentUpper {\n\t\tif r.broken[key] || upper <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, factor := range []lnwire.MilliSatoshi{9, 4, 2} {\n\t\t\tdenominator := lnwire.MilliSatoshi(10)\n\t\t\tif factor == 4 {\n\t\t\t\tdenominator = 5\n\t\t\t}\n\t\t\tif factor == 2 {\n\t\t\t\tdenominator = 3\n\t\t\t}\n\t\t\tbound := upper * factor / denominator\n\t\t\tif bound >= minimum && bound <= amt {\n\t\t\t\tbounds = append(bounds, bound)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] || belief.upperFail <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tbound := belief.upperFail * 4 / 5\n\t\tif bound >= minimum && bound <= amt {\n\t\t\tbounds = append(bounds, bound)\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 6 {\n\t\tbounds = bounds[:6]\n\t}\n\tfor _, bound := range bounds {\n\t\taddCandidate(&values, seen, bound, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\treturn values\n}\n\nfunc (r *candidateRouter) reserveChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\tr.reserved[key] += choice.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) releaseChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= choice.amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - choice.amounts[i]\n\t\t}\n\t}\n}\n\n// continuationScore performs a bounded one-step route-set lookahead. It tests\n// whether the remainder has a plausible route after the candidate's liquidity\n// is reserved. This favors unequal allocations across genuinely independent\n// corridors instead of discovering every split through failed attempts.\nfunc (r *candidateRouter) continuationScore(choice *routeChoice,\n\tremaining lnwire.MilliSatoshi, partsLeft uint32) float64 {\n\n\tif remaining <= choice.deliver {\n\t\treturn 0\n\t}\n\tif partsLeft <= 1 {\n\t\treturn 20\n\t}\n\n\tafter := remaining - choice.deliver\n\tslots := partsLeft - 1\n\tnextMinimum := (after + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\tif nextMinimum < 1_000 {\n\t\tnextMinimum = 1_000\n\t}\n\n\tr.reserveChoice(choice)\n\tdefer r.releaseChoice(choice)\n\n\tnextChoice, err := r.findRoute(nextMinimum)\n\tif err != nil {\n\t\treturn 8\n\t}\n\n\t// Reward a continuation that uses few of the same directed channels.\n\toverlap := 0\n\tused := make(map[edgeKey]bool, len(choice.keys))\n\tfor _, key := range choice.keys {\n\t\tused[key] = true\n\t}\n\tfor _, key := range nextChoice.keys {\n\t\tif used[key] {\n\t\t\toverlap++\n\t\t}\n\t}\n\n\treturn 0.30*-math.Log(nextChoice.probability) +\n\t\t0.16*float64(overlap) +\n\t\tfloat64(nextChoice.fee)/6_000_000\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Estimate the cost of covering the entire remainder with similarly\n\t\t// reliable shards. This prevents a small shard from looking cheap\n\t\t// merely because it exposes less liquidity.\n\t\tshardsNeeded := float64(amt) / float64(shard)\n\t\trisk := -math.Log(choice.probability) * shardsNeeded\n\t\tfees := float64(choice.fee) * shardsNeeded / 3_000_000\n\t\tpartCost := 0.06 * shardsNeeded\n\t\tlookahead := r.continuationScore(\n\t\t\tchoice, amt, partsLeft,\n\t\t)\n\n\t\tchoice.utility = risk + fees + partCost + lookahead\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tr.reserveChoice(best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tfresh := r.paymentUpper[key]\n\tif fresh == 0 || amt < fresh {\n\t\tr.paymentUpper[key] = amt\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 4\n\t} else {\n\t\tbelief.estimate = (3*belief.estimate + amt/4) / 4\n\t}\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\n// learnProbeSuccess records that an edge accepted an HTLC before a downstream\n// failure. Since the attempt did not settle, no liquidity is shifted.\nfunc (r *candidateRouter) learnProbeSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\n\t// Under the bimodal model, accepting a meaningful probe is evidence for\n\t// the liquid mode, but the proven lower bound remains authoritative.\n\toptimistic := edge.capacity * 4 / 5\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= belief.lowerOK {\n\t\tbelief.upperFail = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tif belief.lowerOK >= belief.upperFail && belief.upperFail > 0 {\n\t\tbelief.upperFail = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\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\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\t// Unknown attribution should mildly discourage the whole path, but\n\t\t// must not create false hard liquidity bounds.\n\t\tfor _, key := range keys {\n\t\t\tr.edgeFailures[key]++\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Every edge before the failing edge accepted the HTLC. Preserve that\n\t// positive evidence without applying settlement liquidity movement.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnProbeSuccess(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// The route was built from the advertised directional policy. Avoid\n\t\t// repeating a policy-invalid edge for the remainder of this payment.\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 22,
"parent": 3,
"score": 0.7677,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxRouteAttempts = 96\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tlastFailureAmt map[edgeKey]lnwire.MilliSatoshi\n\tbroken map[edgeKey]bool\n\trouteTries map[uint64]uint32\n\n\tlastFailedDeliver lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tlastFailureAmt: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tbroken: make(map[edgeKey]bool),\n\t\trouteTries: make(map[uint64]uint32),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\n\t\tnormalizeBelief(&belief, edge.capacity)\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc normalizeBelief(belief *liquidityBelief,\n\tcapacity lnwire.MilliSatoshi) {\n\n\tif belief.lowerOK > capacity {\n\t\tbelief.lowerOK = capacity\n\t}\n\tif belief.estimate > capacity {\n\t\tbelief.estimate = capacity\n\t}\n\tif belief.upperFail > capacity {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.upperFail > 0 &&\n\t\tbelief.lowerOK >= belief.upperFail {\n\n\t\tbelief.lowerOK = belief.upperFail - 1\n\t}\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\testimate := belief.estimate\n\tif estimate == 0 {\n\t\testimate = edge.capacity / 2\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif belief.upperFail > belief.lowerOK {\n\t\tinterval := float64(belief.upperFail - belief.lowerOK)\n\t\tif interval/3 < scale {\n\t\t\tscale = interval / 3\n\t\t}\n\t}\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.82, 0.20*float64(belief.samples))\n\tprobability := (1-confidence)*prior + confidence*point\n\n\treturn clampProbability(probability)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tfingerprint uint64\n}\n\nfunc (r *candidateRouter) failurePenalty(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tfailures := r.edgeFailures[edge.key]\n\tif failures == 0 {\n\t\treturn 0\n\t}\n\n\tfailedAmt := r.lastFailureAmt[edge.key]\n\tif failedAmt == 0 {\n\t\treturn 0.20 * float64(failures)\n\t}\n\n\tratio := float64(amt) / float64(failedAmt)\n\tswitch {\n\tcase ratio >= 0.90:\n\t\treturn 0.55 * float64(failures)\n\tcase ratio >= 0.55:\n\t\treturn 0.22 * float64(failures)\n\tcase ratio >= 0.30:\n\t\treturn 0.08 * float64(failures)\n\tdefault:\n\t\treturn 0.02 * float64(failures)\n\t}\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.amt + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 +\n\t\t\t\tr.failurePenalty(edge, item.amt)\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(\n\t\t\tr.edges[key], amounts[i],\n\t\t)\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t\tfingerprint: routeFingerprint(keys, deliver),\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\n\t\t\t\t\"route contains a cycle\",\n\t\t\t)\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc appendEdgeBreakpoints(values *[]lnwire.MilliSatoshi,\n\tedge *candidateEdge, belief liquidityBelief) {\n\n\tcapacity := edge.capacity\n\tif edge.maxHTLC > 0 && edge.maxHTLC < capacity {\n\t\tcapacity = edge.maxHTLC\n\t}\n\n\t*values = append(\n\t\t*values,\n\t\tcapacity*9/10,\n\t\tcapacity*3/4,\n\t)\n\n\tif belief.lowerOK > 0 {\n\t\t*values = append(\n\t\t\t*values,\n\t\t\tbelief.lowerOK,\n\t\t\tbelief.lowerOK*9/10,\n\t\t)\n\t}\n\tif belief.estimate > 0 {\n\t\t*values = append(\n\t\t\t*values,\n\t\t\tbelief.estimate*9/10,\n\t\t\tbelief.estimate*3/4,\n\t\t)\n\t}\n\tif belief.upperFail > 1 {\n\t\t*values = append(\n\t\t\t*values,\n\t\t\t(belief.upperFail-1)*2/3,\n\t\t\t(belief.upperFail-1)/2,\n\t\t\t(belief.upperFail-1)/3,\n\t\t)\n\t}\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 32)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*4/5, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedDeliver > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedDeliver*3/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedDeliver*2/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedDeliver/4,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar breakpoints []lnwire.MilliSatoshi\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tif !r.broken[edge.key] {\n\t\t\tappendEdgeBreakpoints(\n\t\t\t\t&breakpoints, edge, r.beliefs[edge.key],\n\t\t\t)\n\t\t}\n\t}\n\tfor _, edge := range r.edges {\n\t\tif edge.key.from != r.source || r.broken[edge.key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tavailable := r.localBalances[edge.key.chanID] -\n\t\t\tr.reserved[edge.key]\n\t\tif available > 0 {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints,\n\t\t\t\tavailable*9/10,\n\t\t\t\tavailable*3/4,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(breakpoints, func(i, j int) bool {\n\t\treturn breakpoints[i] > breakpoints[j]\n\t})\n\n\tadded := 0\n\tfor _, breakpoint := range breakpoints {\n\t\tbefore := len(values)\n\t\taddCandidate(\n\t\t\t&values, seen, breakpoint, minimum, amt,\n\t\t)\n\t\tif len(values) > before {\n\t\t\tadded++\n\t\t}\n\t\tif added >= 16 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc routeFingerprint(keys []edgeKey,\n\tdeliver lnwire.MilliSatoshi) uint64 {\n\n\tconst (\n\t\toffset = uint64(1469598103934665603)\n\t\tprime = uint64(1099511628211)\n\t)\n\n\thash := offset\n\tmix := func(value uint64) {\n\t\tfor i := 0; i < 8; i++ {\n\t\t\thash ^= value & 0xff\n\t\t\thash *= prime\n\t\t\tvalue >>= 8\n\t\t}\n\t}\n\n\tmix(uint64(deliver))\n\tfor _, key := range keys {\n\t\tmix(key.chanID)\n\t}\n\n\treturn hash\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxRouteAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\trequiredParts := uint32(\n\t\t\t(amt + shard - 1) / shard,\n\t\t)\n\t\tif requiredParts > partsLeft {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Approximate the risk of completing the remaining payment with\n\t\t// similarly sized parts. This avoids repeatedly choosing the\n\t\t// smallest legal shard merely because one tiny attempt is safe.\n\t\tpartCount := float64(requiredParts)\n\t\trisk := -math.Log(choice.probability)\n\t\tfeeCost := partCount *\n\t\t\tfloat64(choice.fee) / 2_500_000\n\t\tretryCost := 0.65 * float64(\n\t\t\tr.routeTries[choice.fingerprint],\n\t\t)\n\n\t\tchoice.utility = partCount*(risk+0.045) +\n\t\t\tfeeCost + retryCost\n\n\t\t// Prefer an exact final shard when choices have similar expected\n\t\t// completion cost.\n\t\tif shard == amt {\n\t\t\tchoice.utility -= 0.03\n\t\t}\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.routeTries[best.fingerprint]++\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(key edgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edges[key]\n\tif edge != nil {\n\t\tnormalizeBelief(&belief, edge.capacity)\n\t}\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\t// Under a bimodal prior, a miss is evidence that this direction is\n\t// likely in its depleted mode. Retain lower-amount retries, but move\n\t// the point estimate substantially below the failed amount.\n\tdepletedEstimate := amt / 5\n\tif belief.estimate == 0 ||\n\t\tdepletedEstimate < belief.estimate {\n\n\t\tbelief.estimate = depletedEstimate\n\t} else {\n\t\tbelief.estimate =\n\t\t\t(3*belief.estimate + depletedEstimate) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = belief.upperFail - 1\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnPass(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\toptimistic := edge.capacity * 9 / 10\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate =\n\t\t\t(belief.estimate + optimistic) / 2\n\t}\n\n\tif belief.upperFail > 0 && belief.upperFail <= amt {\n\t\tbelief.upperFail = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSettlement(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseReservation(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\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\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservation(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSettlement(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedDeliver = 0\n\t\treturn nil\n\t}\n\n\tdeliver := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.lastFailedDeliver = deliver\n\n\tfailIndex := failureEdgeIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\treturn nil\n\t}\n\n\t// Every edge before the failure source successfully forwarded the\n\t// attempt. Since the failed HTLC unwinds, this proves a lower bound\n\t// without moving channel liquidity.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnPass(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\t\tr.edgeFailures[key]++\n\t\tr.lastFailureAmt[key] = failedAmt\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t\tr.edgeFailures[key] += 3\n\t\tr.lastFailureAmt[key] = failedAmt\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 23,
"parent": 3,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype 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 <= 0 || 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\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\trouteFailures map[uint64]uint32\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t\trouteFailures: make(map[uint64]uint32),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.82, 0.24*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tfingerprint uint64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\tbanned map[edgeKey]bool) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif banned[edge.key] || r.broken[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tif !edge.usable(sending) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := sending + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, sending)\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 +\n\t\t\t\t0.38*float64(r.edgeFailures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tedge := r.edges[key]\n\t\tif edge == nil || !edge.usable(amounts[i]) {\n\t\t\treturn nil, errors.New(\"route violates edge constraints\")\n\t\t}\n\t\tprobability *= r.edgeProbability(edge, amounts[i])\n\t}\n\n\tfingerprint := routeFingerprint(keys, deliver)\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tfingerprint: fingerprint,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.usable(amounts[i]) {\n\t\t\treturn nil, nil, nil, errors.New(\n\t\t\t\t\"constructed amount violates policy\",\n\t\t\t)\n\t\t}\n\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc routeFingerprint(keys []edgeKey,\n\tamt lnwire.MilliSatoshi) uint64 {\n\n\thash := uint64(1469598103934665603)\n\tmix := func(value uint64) {\n\t\thash ^= value\n\t\thash *= 1099511628211\n\t}\n\n\tmix(uint64(amt))\n\tfor _, key := range keys {\n\t\tmix(key.chanID)\n\t\tfor _, value := range key.from {\n\t\t\tmix(uint64(value))\n\t\t}\n\t\tfor _, value := range key.to {\n\t\t\tmix(uint64(value))\n\t\t}\n\t}\n\n\treturn hash\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1_000)\n\tif amt < minimum {\n\t\tminimum = amt\n\t}\n\n\taverage := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 20)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, average*3/2, minimum, amt)\n\taddCandidate(&values, seen, average*5/4, minimum, amt)\n\taddCandidate(&values, seen, average, minimum, amt)\n\taddCandidate(&values, seen, average*3/4, minimum, amt)\n\taddCandidate(&values, seen, average/2, minimum, amt)\n\taddCandidate(&values, seen, amt/4, minimum, amt)\n\taddCandidate(&values, seen, amt/8, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/3,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar breakpoints []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK > 0 {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints, belief.lowerOK*9/10,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > 1 {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints,\n\t\t\t\tbelief.upperFail*2/3,\n\t\t\t\tbelief.upperFail/2,\n\t\t\t)\n\t\t}\n\t\tif belief.samples > 0 && belief.estimate > 0 {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints, belief.estimate*3/4,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(breakpoints, func(i, j int) bool {\n\t\tdi := absMSat(breakpoints[i] - average)\n\t\tdj := absMSat(breakpoints[j] - average)\n\t\treturn di < dj\n\t})\n\tif len(breakpoints) > 6 {\n\t\tbreakpoints = breakpoints[:6]\n\t}\n\n\tfor _, value := range breakpoints {\n\t\taddCandidate(\n\t\t\t&values, seen, value, minimum, amt,\n\t\t)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\treturn values\n}\n\nfunc absMSat(value lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif value < 0 {\n\t\treturn -value\n\t}\n\treturn value\n}\n\nfunc samePath(a, b []edgeKey) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif a[i] != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) routeAlternatives(\n\tdeliver lnwire.MilliSatoshi) []*routeChoice {\n\n\tbase, err := r.findRoute(deliver, nil)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tchoices := []*routeChoice{base}\n\tlimit := len(base.keys)\n\tif limit > 7 {\n\t\tlimit = 7\n\t}\n\n\tfor i := 0; i < limit; i++ {\n\t\tbanned := map[edgeKey]bool{\n\t\t\tbase.keys[i]: true,\n\t\t}\n\t\tchoice, err := r.findRoute(deliver, banned)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tduplicate := false\n\t\tfor _, existing := range choices {\n\t\t\tif samePath(existing.keys, choice.keys) {\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\n\t\tchoices = append(choices, choice)\n\t\tif len(choices) >= 4 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn choices\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\taverage := float64(amt) / float64(partsLeft)\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tfor _, choice := range r.routeAlternatives(shard) {\n\t\t\tprobabilityCost := -math.Log(choice.probability)\n\t\t\tfeeCost := float64(choice.fee) / 2_500_000\n\t\t\trouteRetryCost := 1.35 *\n\t\t\t\tfloat64(r.routeFailures[choice.fingerprint])\n\n\t\t\tshardSize := float64(shard)\n\t\t\tpartsPressure := 0.0\n\t\t\tif shardSize < average {\n\t\t\t\tdeficit := (average - shardSize) / average\n\t\t\t\tpartsPressure = 0.55 * deficit * deficit\n\t\t\t} else {\n\t\t\t\tpartsPressure = 0.10 *\n\t\t\t\t\tmath.Log1p(shardSize/average-1)\n\t\t\t}\n\n\t\t\tprogressReward := 0.16 *\n\t\t\t\tmath.Log1p(shardSize/average)\n\t\t\tchoice.utility = probabilityCost + feeCost +\n\t\t\t\trouteRetryCost + partsPressure -\n\t\t\t\tprogressReward\n\n\t\t\tif best == nil || choice.utility < best.utility {\n\t\t\t\tbest = choice\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnPass(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= amt {\n\t\tbelief.upperFail = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tfailureEstimate := amt / 4\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = failureEstimate\n\t} else {\n\t\tbelief.estimate = (\n\t\t\t3*belief.estimate + failureEstimate,\n\t\t) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseReservation(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservation(keys, amounts)\n\n\tdeliver := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tfingerprint := routeFingerprint(keys, deliver)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tdelete(r.routeFailures, fingerprint)\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tr.routeFailures[fingerprint]++\n\tr.lastFailedAmt = deliver\n\n\tfailIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tr.edgeFailures[key]++\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnPass(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\t\tr.edgeFailures[key] += 3\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t\tr.edgeFailures[key] += 8\n\n\tdefault:\n\t\tr.edgeFailures[key] += 2\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 24,
"parent": 1,
"score": 0.6134,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskCostMsat = 300000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\n\tlowerRetryFactor = 0.62\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n\tmaxShardQuotes = 24\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tpreferredShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := spec.MaxParts\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.preferredShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < minProbability:\n\t\treturn minProbability\n\tcase p > maxProbability:\n\t\treturn maxProbability\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.024)\n\thighMode := 0.475 / (1 + math.Exp(15*(x-0.79)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= edge.capacity || amt > edge.capacity-reserved {\n\t\treturn minProbability\n\t}\n\n\teffective := amt + reserved\n\tif edge.key.from == r.source {\n\t\tavailable := r.localBalances[edge.key.chanID]\n\t\tif reserved >= available || amt > available-reserved {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && effective >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif reserved == 0 && belief.lowerOK != 0 &&\n\t\tamt <= belief.lowerOK {\n\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(effective, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.065, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(effective)-float64(belief.estimate))/width,\n\t))\n\n\tweight := 0.48\n\tif belief.lowerOK != 0 || belief.upperBad != 0 {\n\t\tweight = 0.62\n\t}\n\n\treturn clampProbability(\n\t\t(1-weight)*prior + weight*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif probability <= minProbability &&\n\t\t\t\tbelief.upperBad != 0 &&\n\t\t\t\tamtOver+reserved >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t} else {\n\t\t\t\tavailable := r.localBalances[edge.key.chanID]\n\t\t\t\tif reserved >= available ||\n\t\t\t\t\tsending > available-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tcongestion := 0.0\n\t\t\tif reserved > 0 {\n\t\t\t\tcongestion = 90000 *\n\t\t\t\t\tfloat64(reserved) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tcongestion + r.routePenalty[edge.key] + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc addShardCandidate(values map[lnwire.MilliSatoshi]struct{},\n\tvalue, remaining, minimum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > remaining {\n\t\tvalue = remaining\n\t}\n\tif value > 0 {\n\t\tvalues[value] = struct{}{}\n\t}\n}\n\nfunc distanceFromIdeal(value,\n\tideal lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value > ideal {\n\t\treturn value - ideal\n\t}\n\treturn ideal - value\n}\n\nfunc (r *candidateRouter) shardCandidates(\n\tremaining lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tideal := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(partsLeft),\n\t)\n\tminimum := minShardMsat\n\tif remaining < minimum {\n\t\tminimum = remaining\n\t}\n\n\tvalues := make(map[lnwire.MilliSatoshi]struct{})\n\tratios := []float64{\n\t\t0.42, 0.55, 0.68, 0.82, 1.0, 1.18, 1.42, 1.75, 2.2,\n\t}\n\tfor _, ratio := range ratios {\n\t\taddShardCandidate(\n\t\t\tvalues,\n\t\t\tlnwire.MilliSatoshi(float64(ideal)*ratio),\n\t\t\tremaining, minimum,\n\t\t)\n\t}\n\n\taddShardCandidate(\n\t\tvalues, r.preferredShard, remaining, minimum,\n\t)\n\n\tfor _, belief := range r.beliefs {\n\t\tif belief.lowerOK != 0 {\n\t\t\taddShardCandidate(\n\t\t\t\tvalues, belief.lowerOK*97/100,\n\t\t\t\tremaining, minimum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperBad != 0 {\n\t\t\taddShardCandidate(\n\t\t\t\tvalues,\n\t\t\t\tlnwire.MilliSatoshi(\n\t\t\t\t\tfloat64(belief.upperBad)*\n\t\t\t\t\t\tlowerRetryFactor,\n\t\t\t\t),\n\t\t\t\tremaining, minimum,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate != 0 {\n\t\t\taddShardCandidate(\n\t\t\t\tvalues, belief.estimate*82/100,\n\t\t\t\tremaining, minimum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, edge := range r.edgeByKey {\n\t\tif key.from != r.source && key.to != r.spec.Target {\n\t\t\tcontinue\n\t\t}\n\n\t\tavailable := edge.capacity\n\t\tif key.from == r.source {\n\t\t\tavailable = r.localBalances[key.chanID]\n\t\t}\n\t\tif reserved := r.reserved[key]; reserved < available {\n\t\t\tavailable -= reserved\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\taddShardCandidate(\n\t\t\tvalues, available*72/100, remaining, minimum,\n\t\t)\n\t\taddShardCandidate(\n\t\t\tvalues, available*90/100, remaining, minimum,\n\t\t)\n\t}\n\n\tcandidates := make([]lnwire.MilliSatoshi, 0, len(values))\n\tfor value := range values {\n\t\tcandidates = append(candidates, value)\n\t}\n\n\tsort.Slice(candidates, func(i, j int) bool {\n\t\tdi := distanceFromIdeal(candidates[i], ideal)\n\t\tdj := distanceFromIdeal(candidates[j], ideal)\n\t\tif di == dj {\n\t\t\treturn candidates[i] > candidates[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\n\tif len(candidates) > maxShardQuotes {\n\t\tcandidates = candidates[:maxShardQuotes]\n\t}\n\n\treturn candidates\n}\n\nfunc allocationPenalty(shard, ideal,\n\tremaining lnwire.MilliSatoshi) float64 {\n\n\tdifference := float64(distanceFromIdeal(shard, ideal))\n\tweight := 0.0015\n\tif shard < ideal {\n\t\tweight = 0.0022\n\t}\n\n\tpenalty := difference * weight\n\tprogress := float64(shard) / math.Max(float64(remaining), 1)\n\tpenalty -= progress * 45000\n\n\treturn penalty\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tideal := ceilDiv(amt, lnwire.MilliSatoshi(partsLeft))\n\tcandidates := r.shardCandidates(amt, partsLeft)\n\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestScore = math.Inf(1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range candidates {\n\t\trt, routeScore, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := routeScore + allocationPenalty(\n\t\t\tshard, ideal, amt,\n\t\t)\n\t\tif score < bestScore {\n\t\t\tbestScore = score\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt + (edge.capacity-amt)*4/5\n\tif estimate > edge.capacity {\n\t\testimate = edge.capacity\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > 0 {\n\t\t\tr.preferredShard = delivered\n\t\t}\n\t\tr.consecutiveFailures = 0\n\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 3\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.65\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmount := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmount)\n\t\tr.routePenalty[key] += riskCostMsat * 0.72\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif retry >= minShardMsat {\n\t\t\tr.preferredShard = retry\n\t\t}\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 3 {\n\t\tfor penaltyKey, penalty := range r.routePenalty {\n\t\t\tif penaltyKey != key {\n\t\t\t\tr.routePenalty[penaltyKey] = penalty * 0.85\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 25,
"parent": 3,
"score": 0.7755,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, 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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, 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.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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, exists := sharedBeliefs.values[key]\n\t\tif !exists {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\n\tcase p > 0.985:\n\t\treturn 0.985\n\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\tif r.broken[edge.key] || !edge.usable(amt) {\n\t\treturn false\n\t}\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.key.from == r.source {\n\t\treturn total <= r.localBalances[edge.key.chanID]\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\n\t// A failed amount remains unavailable, but smaller probes are allowed.\n\t// This prevents repeated identical attempts without permanently\n\t// blacklisting the channel.\n\treturn belief.upperFail == 0 || total < belief.upperFail\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\tconfidence := math.Min(0.82, 0.20*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, exists := score[item.node]\n\t\tif !exists || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !r.edgeAvailable(edge, item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_500_000 +\n\t\t\t\t0.012 +\n\t\t\t\t0.22*float64(r.edgeFailures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 26)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*7/8, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*4/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tbreakpoints := make([]lnwire.MilliSatoshi, 0, len(r.edges)*2)\n\tfor key, edge := range r.edges {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tavailable := edge.capacity - r.reserved[key]\n\t\tif key.from == r.source {\n\t\t\tavailable = r.localBalances[key.chanID] -\n\t\t\t\tr.reserved[key]\n\t\t}\n\t\tif available >= minimum {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints,\n\t\t\t\tavailable*7/8,\n\t\t\t\tavailable*3/4,\n\t\t\t)\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tif belief.lowerOK >= minimum {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints, belief.lowerOK,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate >= minimum {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints, belief.estimate*9/10,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > minimum {\n\t\t\tbreakpoints = append(\n\t\t\t\tbreakpoints, belief.upperFail*4/5,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(breakpoints, func(i, j int) bool {\n\t\treturn breakpoints[i] > breakpoints[j]\n\t})\n\n\tadded := 0\n\tfor _, value := range breakpoints {\n\t\tbefore := len(values)\n\t\taddCandidate(&values, seen, value, minimum, amt)\n\t\tif len(values) != before {\n\t\t\tadded++\n\t\t}\n\t\tif added >= 14 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc choiceCost(choice *routeChoice) float64 {\n\treturn -math.Log(choice.probability) +\n\t\tfloat64(choice.fee)/2_500_000\n}\n\nfunc (r *candidateRouter) reserveChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\tr.reserved[key] += choice.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) releaseChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\tvalue := r.reserved[key]\n\t\tif value <= choice.amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = value - choice.amounts[i]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) projectedRemainderCost(\n\tremaining lnwire.MilliSatoshi, slots uint32) (float64, bool) {\n\n\tif remaining <= 0 {\n\t\treturn 0, true\n\t}\n\tif slots == 0 {\n\t\treturn 0, false\n\t}\n\n\tbest := math.Inf(1)\n\tfor _, shard := range r.candidateAmounts(remaining, slots) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tcount := math.Ceil(\n\t\t\tfloat64(remaining) / float64(shard),\n\t\t)\n\t\tif count > float64(slots) {\n\t\t\tcontinue\n\t\t}\n\n\t\tcost := choiceCost(choice)*count +\n\t\t\t0.05*(count-1)\n\t\tif cost < best {\n\t\t\tbest = cost\n\t\t}\n\t}\n\n\treturn best, !math.IsInf(best, 1)\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tutility := choiceCost(choice)\n\t\tremaining := amt - shard\n\n\t\tif remaining > 0 {\n\t\t\tif partsLeft <= 1 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.reserveChoice(choice)\n\t\t\tremainderCost, feasible := r.projectedRemainderCost(\n\t\t\t\tremaining, partsLeft-1,\n\t\t\t)\n\t\t\tr.releaseChoice(choice)\n\n\t\t\tif !feasible {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tutility += remainderCost\n\t\t\tutility += 0.035 *\n\t\t\t\t(float64(amt)/float64(shard) - 1)\n\t\t}\n\n\t\tchoice.utility = utility\n\t\tif best == nil ||\n\t\t\tchoice.utility < best.utility-1e-12 ||\n\t\t\t(math.Abs(choice.utility-best.utility) <= 1e-12 &&\n\t\t\t\tchoice.deliver > best.deliver) {\n\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no jointly feasible route set found\")\n\t}\n\n\tr.reserveChoice(best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 4\n\t} else {\n\t\tbelief.estimate = (3*belief.estimate + amt/4) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnPass(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= amt {\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\tvalue := r.reserved[key]\n\t\tif value <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = value - amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\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\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tr.lastFailedAmt =\n\t\t\trt.Hops[len(rt.Hops)-1].AmtToForward\n\n\t\treturn nil\n\t}\n\n\t// Every edge before the failing node carried the HTLC successfully.\n\t// Record that evidence without shifting liquidity because a failed HTLC\n\t// is rolled back.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnPass(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.lastFailedAmt =\n\t\trt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.edgeFailures[key]++\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 26,
"parent": 1,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskCostMsat = 260000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\n\tlowerRetryFactor = 0.68\n\tmaxNodeLabels = 12\n\tmaxShardQuotes = 12\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(\n\tamt lnwire.MilliSatoshi) bool {\n\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\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tunknownCost map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures 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(\"payment specification is nil\")\n\t}\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tunknownCost: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tdesiredParts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tdesiredParts = 6\n\tcase spec.Amount > 250_000_000:\n\t\tdesiredParts = 4\n\tcase spec.Amount > 50_000_000:\n\t\tdesiredParts = 2\n\t}\n\n\tif spec.MaxParts != 0 && desiredParts > spec.MaxParts {\n\t\tdesiredParts = spec.MaxParts\n\t}\n\tif desiredParts == 0 {\n\t\tdesiredParts = 1\n\t}\n\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(desiredParts),\n\t)\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{\n\t\tsource: true,\n\t}\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.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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\n\treturn a/b + boolMSat(a%b != 0)\n}\n\nfunc boolMSat(value bool) lnwire.MilliSatoshi {\n\tif value {\n\t\treturn 1\n\t}\n\n\treturn 0\n}\n\nfunc scaledAmount(amt lnwire.MilliSatoshi,\n\tfactor float64) lnwire.MilliSatoshi {\n\n\tif amt <= 0 || factor <= 0 {\n\t\treturn 0\n\t}\n\n\tvalue := float64(amt) * factor\n\tif value >= float64(math.MaxInt64) {\n\t\treturn lnwire.MilliSatoshi(math.MaxInt64)\n\t}\n\n\treturn lnwire.MilliSatoshi(value)\n}\n\nfunc clampProbability(probability float64) float64 {\n\tif probability < minProbability {\n\t\treturn minProbability\n\t}\n\tif probability > maxProbability {\n\t\treturn maxProbability\n\t}\n\n\treturn probability\n}\n\nfunc bimodalPrior(amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-ratio/0.025)\n\thighMode := 0.48 / (\n\t\t1 + math.Exp(14*(ratio-0.78)),\n\t)\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) availableLocal(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\treserved := r.reserved[key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) availableCapacity(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= edge.capacity {\n\t\treturn 0\n\t}\n\n\treturn edge.capacity - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.availableLocal(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.52*prior + 0.48*point)\n}\n\ntype pathLabel struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\n\tedge *candidateEdge\n\tchild *pathLabel\n\n\tactive bool\n\tindex int\n}\n\ntype labelQueue []*pathLabel\n\nfunc (q labelQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q labelQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q labelQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *labelQueue) Push(value any) {\n\tlabel := value.(*pathLabel)\n\tlabel.index = len(*q)\n\t*q = append(*q, label)\n}\n\nfunc (q *labelQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\tlabel := old[last]\n\told[last] = nil\n\tlabel.index = -1\n\t*q = old[:last]\n\n\treturn label\n}\n\nfunc labelDominates(a, b *pathLabel) bool {\n\treturn a.score <= b.score && a.amount <= b.amount\n}\n\nfunc admitLabel(labels map[route.Vertex][]*pathLabel,\n\tcandidate *pathLabel) bool {\n\n\tnodeLabels := labels[candidate.node]\n\tfor _, existing := range nodeLabels {\n\t\tif existing.active && labelDominates(existing, candidate) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tactiveCount := 0\n\tfor _, existing := range nodeLabels {\n\t\tif !existing.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif labelDominates(candidate, existing) {\n\t\t\texisting.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tactiveCount++\n\t}\n\n\tif activeCount >= maxNodeLabels {\n\t\tvar worst *pathLabel\n\t\tfor _, existing := range nodeLabels {\n\t\t\tif !existing.active {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst == nil || existing.score > worst.score {\n\t\t\t\tworst = existing\n\t\t\t}\n\t\t}\n\n\t\tif worst != nil && candidate.score >= worst.score {\n\t\t\treturn false\n\t\t}\n\t\tif worst != nil {\n\t\t\tworst.active = false\n\t\t}\n\t}\n\n\tcandidate.active = true\n\tlabels[candidate.node] = append(nodeLabels, candidate)\n\n\treturn true\n}\n\nfunc (r *candidateRouter) findPath(\n\tamt lnwire.MilliSatoshi) ([]*candidateEdge, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\ttarget := &pathLabel{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\n\tlabels := map[route.Vertex][]*pathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &labelQueue{}\n\theap.Push(pq, target)\n\n\tvar sourceLabel *pathLabel\n\n\tfor pq.Len() != 0 {\n\t\tcurrent := heap.Pop(pq).(*pathLabel)\n\t\tif !current.active {\n\t\t\tcontinue\n\t\t}\n\t\tif current.node == r.source {\n\t\t\tsourceLabel = current\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[current.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOverEdge := current.amount\n\t\t\tif !edge.usable(amtOverEdge) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif amtOverEdge > r.availableCapacity(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOverEdge)\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOverEdge >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOverEdge\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tif sending > r.availableLocal(edge.key) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfee = edge.fee(amtOverEdge)\n\t\t\t\tif fee > lnwire.MilliSatoshi(\n\t\t\t\t\tmath.MaxInt64,\n\t\t\t\t)-sending {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tscore := current.score + float64(fee) +\n\t\t\t\t(-math.Log(probability) * riskCostMsat) +\n\t\t\t\tr.unknownCost[edge.key] + 200\n\n\t\t\tnext := &pathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tamount: sending,\n\t\t\t\tscore: score,\n\t\t\t\tedge: edge,\n\t\t\t\tchild: current,\n\t\t\t}\n\t\t\tif !admitLabel(labels, next) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\theap.Push(pq, next)\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, 8)\n\tfor label := sourceLabel; label != nil &&\n\t\tlabel.node != r.spec.Target; label = label.child {\n\n\t\tif label.edge == nil || label.child == nil {\n\t\t\treturn nil, 0, errors.New(\"broken path label\")\n\t\t}\n\n\t\tpath = append(path, label.edge)\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, 0, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 0, errors.New(\"empty route\")\n\t}\n\n\treturn path, sourceLabel.score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tfee := forwardingEdge.fee(amtOver[i+1])\n\t\tif fee > lnwire.MilliSatoshi(\n\t\t\tmath.MaxInt64,\n\t\t)-amtOver[i+1] {\n\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tpath, score, err := r.findPath(amt)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, score, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, exists := r.edgeByKey[key]\n\n\treturn key, exists\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(\n\trt *route.Route) lnwire.MilliSatoshi {\n\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserved[key] += routeAmount(rt, i)\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendShardCandidate(candidates *[]lnwire.MilliSatoshi,\n\tvalue, minimum, maximum lnwire.MilliSatoshi) {\n\n\tif value < minimum || value > maximum || value <= 0 {\n\t\treturn\n\t}\n\n\t*candidates = append(*candidates, value)\n}\n\nfunc uniqueShardCandidates(\n\tvalues []lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] < values[j]\n\t})\n\n\tresult := values[:0]\n\tfor _, value := range values {\n\t\tif len(result) != 0 &&\n\t\t\tresult[len(result)-1] == value {\n\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, value)\n\t}\n\n\tif len(result) <= maxShardQuotes {\n\t\treturn result\n\t}\n\n\tsampled := make([]lnwire.MilliSatoshi, 0, maxShardQuotes)\n\tfor i := 0; i < maxShardQuotes; i++ {\n\t\tindex := i * (len(result) - 1) /\n\t\t\t(maxShardQuotes - 1)\n\t\tvalue := result[index]\n\t\tif len(sampled) == 0 ||\n\t\t\tsampled[len(sampled)-1] != value {\n\n\t\t\tsampled = append(sampled, value)\n\t\t}\n\t}\n\n\treturn sampled\n}\n\nfunc (r *candidateRouter) shardCandidates(\n\tremaining lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tminimum := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(partsLeft),\n\t)\n\n\tbase := r.baseShard\n\tif base < minimum {\n\t\tbase = minimum\n\t}\n\tif base > remaining {\n\t\tbase = remaining\n\t}\n\n\tvalues := make([]lnwire.MilliSatoshi, 0, 16)\n\tappendShardCandidate(\n\t\t&values, minimum, minimum, remaining,\n\t)\n\tappendShardCandidate(\n\t\t&values, base, minimum, remaining,\n\t)\n\n\tfor _, factor := range []float64{\n\t\t1.18, 1.42, 1.75, 2.15, 2.75,\n\t} {\n\t\tappendShardCandidate(\n\t\t\t&values, scaledAmount(base, factor),\n\t\t\tminimum, remaining,\n\t\t)\n\t}\n\n\tif partsLeft <= 3 {\n\t\tappendShardCandidate(\n\t\t\t&values, remaining, minimum, remaining,\n\t\t)\n\t}\n\n\tvar largestKnownOK lnwire.MilliSatoshi\n\tvar largestEstimate lnwire.MilliSatoshi\n\tvar lowerRetry lnwire.MilliSatoshi\n\n\tfor _, belief := range r.beliefs {\n\t\tif belief.lowerOK >= minimum &&\n\t\t\tbelief.lowerOK <= remaining &&\n\t\t\tbelief.lowerOK > largestKnownOK {\n\n\t\t\tlargestKnownOK = belief.lowerOK\n\t\t}\n\n\t\tif belief.estimate >= minimum &&\n\t\t\tbelief.estimate <= remaining &&\n\t\t\tbelief.estimate > largestEstimate {\n\n\t\t\tlargestEstimate = belief.estimate\n\t\t}\n\n\t\tif belief.upperBad != 0 {\n\t\t\tretry := scaledAmount(\n\t\t\t\tbelief.upperBad, lowerRetryFactor,\n\t\t\t)\n\t\t\tif retry >= minimum && retry <= remaining &&\n\t\t\t\tretry > lowerRetry {\n\n\t\t\t\tlowerRetry = retry\n\t\t\t}\n\t\t}\n\t}\n\n\tappendShardCandidate(\n\t\t&values, largestKnownOK, minimum, remaining,\n\t)\n\tappendShardCandidate(\n\t\t&values, largestEstimate, minimum, remaining,\n\t)\n\tappendShardCandidate(\n\t\t&values, lowerRetry, minimum, remaining,\n\t)\n\n\treturn uniqueShardCandidates(values)\n}\n\nfunc (r *candidateRouter) routeQuality(\n\trt *route.Route,\n\tsearchScore float64,\n\tminimum lnwire.MilliSatoshi) float64 {\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered <= 0 {\n\t\treturn math.Inf(-1)\n\t}\n\n\tlogProbability := 0.0\n\tfees := rt.TotalAmount - delivered\n\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability := r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t\tlogProbability += math.Log(probability)\n\t}\n\n\tsizeGain := math.Log(\n\t\tfloat64(delivered) / float64(minimum),\n\t)\n\n\treturn logProbability + 0.88*sizeGain -\n\t\tfloat64(fees)/5_000_000 -\n\t\tsearchScore/50_000_000\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 is zero\")\n\t}\n\tif r.spec.MaxParts == 0 {\n\t\treturn nil, errors.New(\"multipart limit is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tminimum := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\n\tvar bestRoute *route.Route\n\tbestQuality := math.Inf(-1)\n\tvar lastErr error\n\n\tfor _, shard := range r.shardCandidates(\n\t\tamt, partsLeft,\n\t) {\n\t\trt, score, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tquality := r.routeQuality(rt, score, minimum)\n\t\tif bestRoute == nil || quality > bestQuality {\n\t\t\tbestRoute = rt\n\t\t\tbestQuality = quality\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(\n\tkey candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tremaining := edge.capacity - amt\n\tif remaining > 0 {\n\t\testimate += remaining * 3 / 4\n\t}\n\tif estimate > edge.capacity {\n\t\testimate = edge.capacity\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(\n\tkey candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 35 / 100\n\tif belief.estimate == 0 ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(\n\trt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) penalizeUnknownRoute(\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.unknownCost[key] += riskCostMsat / 3\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64,\n\trt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"attempt route is nil\")\n\t}\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif !exists {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.unknownCost[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tr.penalizeUnknownRoute(rt)\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\tr.unknownCost[key] *= 0.50\n\t}\n\n\tkey, exists := r.routeEdge(rt, failIndex)\n\tif !exists {\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(\n\t\t\tkey, routeAmount(rt, failIndex),\n\t\t)\n\n\tdefault:\n\t\tr.recordFailure(\n\t\t\tkey, routeAmount(rt, failIndex),\n\t\t)\n\t\tr.unknownCost[key] += riskCostMsat / 2\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tretry := scaledAmount(\n\t\tdelivered, lowerRetryFactor,\n\t)\n\tif retry > 0 && retry < r.baseShard {\n\t\tr.baseShard = retry\n\t}\n\n\treturn nil\n}\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\n\t\t\"candidateRouter(source=%v,target=%v)\",\n\t\tr.source, r.spec.Target,\n\t)\n}"
}
},
{
"id": 27,
"parent": 1,
"score": 0.6497,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskCostMsat = 275000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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}\n\nvar sharedCandidateBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := spec.MaxParts\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tswitch {\n\tcase spec.Amount <= 50_000_000 && parts > 1:\n\t\tparts = 1\n\tcase spec.Amount <= 250_000_000 && parts > 3:\n\t\tparts = 3\n\tcase spec.Amount <= 1_000_000_000 && parts > 6:\n\t\tparts = 6\n\tcase parts > 10:\n\t\tparts = 10\n\t}\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\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.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.edgeByKey[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\tsharedCandidateBeliefs.Lock()\n\tfor key, belief := range sharedCandidateBeliefs.values {\n\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedCandidateBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < minProbability:\n\t\treturn minProbability\n\tcase p > maxProbability:\n\t\treturn maxProbability\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.026)\n\thighMode := 0.475 / (1 + math.Exp(15*(x-0.79)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\tspent := r.localSpent[key.chanID]\n\tif spent >= available {\n\t\treturn 0\n\t}\n\tavailable -= spent\n\n\treserved := r.reserved[key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.065, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tweight := 0.52\n\tif belief.lowerOK != 0 || belief.upperBad != 0 {\n\t\tweight = 0.68\n\t}\n\n\treturn clampProbability(\n\t\t(1-weight)*prior + weight*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, exists := dist[item.node]\n\t\tif !exists || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif probability <= minProbability &&\n\t\t\t\tbelief.upperBad != 0 {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, exists := dist[r.source]\n\tif !exists {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, exists := r.edgeByKey[key]\n\n\treturn key, exists\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeProbability(rt *route.Route) float64 {\n\tprobability := 1.0\n\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability *= r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t}\n\n\treturn clampProbability(probability)\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendCandidate(candidates *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, amt,\n\tlimit lnwire.MilliSatoshi) {\n\n\tif amt <= 0 || amt > limit || seen[amt] {\n\t\treturn\n\t}\n\tif amt < minShardMsat && limit >= minShardMsat {\n\t\treturn\n\t}\n\n\tseen[amt] = true\n\t*candidates = append(*candidates, amt)\n}\n\nfunc (r *candidateRouter) shardCandidates(\n\tamt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tcandidates := make([]lnwire.MilliSatoshi, 0, 28)\n\n\tappendCandidate(&candidates, seen, amt, amt)\n\tappendCandidate(&candidates, seen, r.baseShard, amt)\n\n\tdivisor := partsLeft\n\tif divisor == 0 {\n\t\tdivisor = 1\n\t}\n\taverage := ceilDiv(amt, lnwire.MilliSatoshi(divisor))\n\tappendCandidate(&candidates, seen, average, amt)\n\n\tfor _, ratio := range []struct {\n\t\tnumerator int64\n\t\tdenominator int64\n\t}{\n\t\t{5, 6},\n\t\t{3, 4},\n\t\t{2, 3},\n\t\t{1, 2},\n\t\t{2, 5},\n\t\t{1, 3},\n\t\t{1, 4},\n\t} {\n\t\tvalue := lnwire.MilliSatoshi(\n\t\t\tint64(amt) * ratio.numerator /\n\t\t\t\tratio.denominator,\n\t\t)\n\t\tappendCandidate(&candidates, seen, value, amt)\n\t}\n\n\tappendCandidate(\n\t\t&candidates, seen, r.baseShard*4/5, amt,\n\t)\n\tappendCandidate(\n\t\t&candidates, seen, r.baseShard*2/3, amt,\n\t)\n\tappendCandidate(\n\t\t&candidates, seen, r.baseShard/2, amt,\n\t)\n\n\taddedEvidence := 0\n\tfor key, belief := range r.beliefs {\n\t\tif addedEvidence >= 16 {\n\t\t\tbreak\n\t\t}\n\t\tif r.blocked[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tbefore := len(candidates)\n\t\tif belief.lowerOK != 0 {\n\t\t\tappendCandidate(\n\t\t\t\t&candidates, seen,\n\t\t\t\tbelief.lowerOK*97/100, amt,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate != 0 {\n\t\t\tappendCandidate(\n\t\t\t\t&candidates, seen,\n\t\t\t\tbelief.estimate*9/10, amt,\n\t\t\t)\n\t\t}\n\t\tif belief.upperBad > minShardMsat {\n\t\t\tappendCandidate(\n\t\t\t\t&candidates, seen,\n\t\t\t\tbelief.upperBad*2/3, amt,\n\t\t\t)\n\t\t\tappendCandidate(\n\t\t\t\t&candidates, seen,\n\t\t\t\tbelief.upperBad*9/20, amt,\n\t\t\t)\n\t\t}\n\t\tif len(candidates) != before {\n\t\t\taddedEvidence++\n\t\t}\n\t}\n\n\tif amt < minShardMsat {\n\t\tappendCandidate(&candidates, seen, amt, amt)\n\t}\n\n\treturn candidates\n}\n\nfunc routeFees(rt *route.Route) lnwire.MilliSatoshi {\n\tdelivered := deliveredAmount(rt)\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts &&\n\t\tr.spec.MaxParts != 0 {\n\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tcandidates := r.shardCandidates(amt, partsLeft)\n\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestUtility = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range candidates {\n\t\trt, pathScore, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := r.routeProbability(rt)\n\t\tfees := routeFees(rt)\n\n\t\tutility := math.Log(float64(shard)+1) +\n\t\t\t1.85*math.Log(probability) -\n\t\t\tfloat64(fees)/2_000_000 -\n\t\t\tpathScore/(riskCostMsat*45)\n\n\t\tif shard == amt {\n\t\t\tutility += 0.42\n\t\t}\n\t\tif shard >= amt*3/4 {\n\t\t\tutility += 0.08\n\t\t}\n\t\tif r.consecutiveFailures >= 2 &&\n\t\t\tshard < r.baseShard {\n\n\t\t\tutility += 0.18\n\t\t}\n\n\t\tif utility > bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edgeByKey[key]\n\tif edge != nil {\n\t\tif belief.lowerOK > edge.capacity {\n\t\t\tbelief.lowerOK = edge.capacity\n\t\t}\n\t\tif belief.estimate > edge.capacity {\n\t\t\tbelief.estimate = edge.capacity\n\t\t}\n\t\tif belief.upperBad > edge.capacity {\n\t\t\tbelief.upperBad = edge.capacity\n\t\t}\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedCandidateBeliefs.Lock()\n\tsharedCandidateBeliefs.values[key] = belief\n\tsharedCandidateBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordProbeSuccess(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 4 / 5\n\t}\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 3 / 10\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.estimate < belief.lowerOK {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) consumeLiquidity(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\tif preEstimate == amt && edge.capacity > amt {\n\t\tpreEstimate += (edge.capacity - amt) * 4 / 5\n\t}\n\n\tif preEstimate > amt {\n\t\tbelief.estimate = preEstimate - amt\n\t} else {\n\t\tbelief.estimate = 0\n\t}\n\n\tif belief.lowerOK > amt {\n\t\tbelief.lowerOK -= amt\n\t} else {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tif belief.upperBad > amt {\n\t\tbelief.upperBad -= amt\n\t} else {\n\t\tbelief.upperBad = 0\n\t}\n\n\tr.saveBelief(key, belief)\n\tr.creditReverse(key, amt)\n}\n\nfunc (r *candidateRouter) creditReverse(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\treverse := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\tedge := r.edgeByKey[reverse]\n\tif edge == nil || reverse.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[reverse]\n\tif belief.estimate+amt > edge.capacity {\n\t\tbelief.estimate = edge.capacity\n\t} else {\n\t\tbelief.estimate += amt\n\t}\n\n\tif belief.lowerOK+amt > edge.capacity {\n\t\tbelief.lowerOK = edge.capacity\n\t} else {\n\t\tbelief.lowerOK += amt\n\t}\n\n\tif belief.upperBad != 0 {\n\t\tif belief.upperBad+amt > edge.capacity {\n\t\t\tbelief.upperBad = 0\n\t\t} else {\n\t\t\tbelief.upperBad += amt\n\t\t}\n\t}\n\n\tr.saveBelief(reverse, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(\n\trt *route.Route, source route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif !exists {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamt := routeAmount(rt, i)\n\t\t\tr.recordProbeSuccess(key, amt)\n\t\t\tr.consumeLiquidity(key, amt)\n\t\t\tr.routePenalty[key] *= 0.2\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif exists {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 3\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.recordProbeSuccess(\n\t\t\t\tkey, routeAmount(rt, i),\n\t\t\t)\n\t\t\tr.routePenalty[key] *= 0.7\n\t\t}\n\t}\n\n\tkey, exists := r.routeEdge(rt, failIndex)\n\tif !exists {\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmount := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmount)\n\t\tr.routePenalty[key] += riskCostMsat\n\n\t\tretry := failedAmount * 2 / 3\n\t\tif retry > minShardMsat &&\n\t\t\t(retry < r.baseShard || r.baseShard == 0) {\n\n\t\t\tr.baseShard = retry\n\t\t}\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 3 {\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := delivered * 9 / 20\n\t\tif retry >= minShardMsat &&\n\t\t\tretry < r.baseShard {\n\n\t\t\tr.baseShard = retry\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 28,
"parent": 1,
"score": 0.8647,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.68\n\tmaxShardChoices = 96\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n\tfailureCeiling lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 250_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 50_000_000:\n\t\tparts = 2\n\t}\n\tif parts > spec.MaxParts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampAmount(amt, capacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amt < 0 {\n\t\treturn 0\n\t}\n\tif amt > capacity {\n\t\treturn capacity\n\t}\n\treturn amt\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) availableLocal(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\treserved := r.reserved[key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.availableLocal(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tweight := 0.48\n\tif belief.lowerOK != 0 || belief.upperBad != 0 {\n\t\tweight = 0.64\n\t}\n\n\treturn clampProbability(\n\t\t(1-weight)*prior + weight*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.availableLocal(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\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\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\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\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendShardChoice(choices *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, candidate, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif candidate < minimum || candidate > maximum ||\n\t\tcandidate <= 0 || seen[candidate] ||\n\t\tlen(*choices) >= maxShardChoices {\n\n\t\treturn\n\t}\n\n\tseen[candidate] = true\n\t*choices = append(*choices, candidate)\n}\n\nfunc (r *candidateRouter) shardChoices(amt,\n\tminNeeded lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tchoices := make([]lnwire.MilliSatoshi, 0, 32)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tappendChoice := func(candidate lnwire.MilliSatoshi) {\n\t\tappendShardChoice(\n\t\t\t&choices, seen, candidate, minNeeded, amt,\n\t\t)\n\t}\n\n\tappendChoice(minNeeded)\n\tappendChoice(amt)\n\tappendChoice(r.baseShard)\n\n\tfor parts := uint32(1); parts <= partsLeft; parts++ {\n\t\tappendChoice(ceilDiv(\n\t\t\tamt, lnwire.MilliSatoshi(parts),\n\t\t))\n\t}\n\n\tfor _, ratio := range []int64{\n\t\t90, 82, 74, 68, 60, 52, 45, 38,\n\t} {\n\t\tappendChoice(amt * lnwire.MilliSatoshi(ratio) / 100)\n\t}\n\n\tfor _, belief := range r.beliefs {\n\t\tif len(choices) >= maxShardChoices {\n\t\t\tbreak\n\t\t}\n\n\t\tappendChoice(belief.lowerOK)\n\t\tappendChoice(belief.lowerOK * 9 / 10)\n\t\tappendChoice(belief.estimate * 4 / 5)\n\t\tif belief.upperBad != 0 {\n\t\t\tappendChoice(belief.upperBad * 2 / 3)\n\t\t\tif belief.upperBad > 1 {\n\t\t\t\tappendChoice(belief.upperBad - 1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.failureCeiling != 0 {\n\t\tfiltered := choices[:0]\n\t\tfor _, choice := range choices {\n\t\t\tif choice <= r.failureCeiling ||\n\t\t\t\tchoice == minNeeded {\n\n\t\t\t\tfiltered = append(filtered, choice)\n\t\t\t}\n\t\t}\n\t\tchoices = filtered\n\t}\n\n\treturn choices\n}\n\nfunc (r *candidateRouter) routeQuality(rt *route.Route,\n\tminNeeded lnwire.MilliSatoshi) float64 {\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered <= 0 {\n\t\treturn math.Inf(1)\n\t}\n\n\tnegativeLogProbability := 0.0\n\tpenalty := 0.0\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn math.Inf(1)\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability := r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t\tnegativeLogProbability -= math.Log(probability)\n\t\tpenalty += r.routePenalty[key] / riskCostMsat\n\t}\n\n\tfee := rt.TotalAmount - delivered\n\tfeeCost := 60000 * float64(fee) / float64(delivered)\n\n\tsizeBenefit := 0.0\n\tif minNeeded > 0 && delivered > minNeeded {\n\t\tsizeBenefit = 0.20 * math.Log(\n\t\t\tfloat64(delivered) / float64(minNeeded),\n\t\t)\n\t}\n\n\thopCost := 0.004 * float64(len(rt.Hops))\n\treturn negativeLogProbability + feeCost + penalty +\n\t\thopCost - sizeBenefit\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.failureCeiling = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tminNeeded := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\n\tchoices := r.shardChoices(amt, minNeeded, partsLeft)\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestQuality = math.Inf(1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range choices {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tquality := r.routeQuality(rt, minNeeded)\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\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edgeByKey[key]\n\tif edge != nil {\n\t\tbelief.lowerOK = clampAmount(\n\t\t\tbelief.lowerOK, edge.capacity,\n\t\t)\n\t\tbelief.estimate = clampAmount(\n\t\t\tbelief.estimate, edge.capacity,\n\t\t)\n\t\tif belief.upperBad > edge.capacity {\n\t\t\tbelief.upperBad = 0\n\t\t}\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt + (edge.capacity-amt)*4/5\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) settleLiquidity(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tbalance := r.localBalances[key.chanID]\n\t\tif amt >= balance {\n\t\t\tr.localBalances[key.chanID] = 0\n\t\t} else {\n\t\t\tr.localBalances[key.chanID] = balance - amt\n\t\t}\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tprovenBefore := belief.lowerOK\n\tif amt > provenBefore {\n\t\tprovenBefore = amt\n\t}\n\n\tif provenBefore > amt {\n\t\tbelief.lowerOK = provenBefore - amt\n\t} else {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tif belief.upperBad > amt {\n\t\tbelief.upperBad -= amt\n\t} else {\n\t\tbelief.upperBad = 0\n\t}\n\n\tpreEstimate := belief.estimate\n\tminimumEstimate := amt + (edge.capacity-amt)*4/5\n\tif preEstimate < minimumEstimate {\n\t\tpreEstimate = minimumEstimate\n\t}\n\tif preEstimate > amt {\n\t\tbelief.estimate = preEstimate - amt\n\t} else {\n\t\tbelief.estimate = 0\n\t}\n\n\tr.saveBelief(key, belief)\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edgeByKey[reverseKey]\n\tif reverseEdge == nil || reverseKey.from == r.source {\n\t\treturn\n\t}\n\n\treverse := r.beliefs[reverseKey]\n\tif reverse.lowerOK < amt {\n\t\treverse.lowerOK = amt\n\t}\n\treverse.estimate = clampAmount(\n\t\treverse.estimate+amt, reverseEdge.capacity,\n\t)\n\tif reverse.estimate < reverse.lowerOK {\n\t\treverse.estimate = reverse.lowerOK\n\t}\n\tif reverse.upperBad != 0 &&\n\t\treverse.upperBad <= reverse.lowerOK {\n\n\t\treverse.upperBad = 0\n\t}\n\n\tr.saveBelief(reverseKey, reverse)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 35 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(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) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamt := routeAmount(rt, i)\n\t\t\tr.recordPass(key, amt)\n\t\t\tr.settleLiquidity(key, amt)\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\tr.failureCeiling = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 2\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.75\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmount := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmount)\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 2 {\n\t\tdelivered := deliveredAmount(rt)\n\t\tceiling := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif ceiling > 0 &&\n\t\t\t(r.failureCeiling == 0 ||\n\t\t\t\tceiling < r.failureCeiling) {\n\n\t\t\tr.failureCeiling = ceiling\n\t\t}\n\t\tif ceiling > 0 && ceiling < r.baseShard {\n\t\t\tr.baseShard = ceiling\n\t\t}\n\t}\n\n\treturn nil\n}"
},
"role": "best"
},
{
"id": 29,
"parent": 2,
"score": 0.7261,
"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\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 280000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tdiversityCost = 1800000.0\n\tminimumShard = lnwire.MilliSatoshi(10_000)\n\tmaxPlanCorridors = 10\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < 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\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype pathCandidate struct {\n\tedges []*candidateEdge\n\thardLimit lnwire.MilliSatoshi\n\tsafeLimit lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n\tretryHint lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\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.edgeByKey[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.\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.edgeByKey[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\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amount >= value {\n\t\treturn 0\n\t}\n\treturn value - amount\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\tavailable = subtractFloor(available, r.localSpent[key.chanID])\n\tavailable = subtractFloor(available, r.reserved[key])\n\n\treturn available\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.06, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tweight := 0.48\n\tif belief.lowerOK != 0 || belief.upperBad != 0 {\n\t\tweight = 0.66\n\t}\n\n\treturn clampProbability((1-weight)*prior + weight*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]int) ([]*candidateEdge, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tsettled := make(map[route.Vertex]bool)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score != bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif settled[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tsettled[item.node] = true\n\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 settled[edge.key.from] || r.blocked[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\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 && amtOver >= belief.upperBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\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\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tcost := float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 1000\n\n\t\t\tif count := diversity[edge.key]; count != 0 {\n\t\t\t\tcost += float64(count) * diversityCost\n\t\t\t}\n\t\t\tif cost < 1 {\n\t\t\t\tcost = 1\n\t\t\t}\n\n\t\t\tscore := item.score + cost\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok || !settled[r.source] {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvisited := make(map[route.Vertex]bool)\n\tpath := make([]*candidateEdge, 0)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, 0, errors.New(\"route contains a cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok || edge.key.from != node {\n\t\t\treturn nil, 0, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tif visited[edge.key.to] && edge.key.to != r.spec.Target {\n\t\t\treturn nil, 0, errors.New(\"route contains a cycle\")\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 0, errors.New(\"empty route\")\n\t}\n\n\treturn path, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.usable(amtOver[i]) {\n\t\t\treturn nil, errors.New(\"path cannot carry amount\")\n\t\t}\n\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(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 len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeFeasible(rt *route.Route) bool {\n\tvisited := map[route.Vertex]bool{\n\t\trt.SourcePubKey: true,\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif visited[hop.PubKeyBytes] {\n\t\t\treturn false\n\t\t}\n\t\tvisited[hop.PubKeyBytes] = true\n\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok || r.blocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tamt := routeAmount(rt, i)\n\t\tif edge == nil || !edge.usable(amt) {\n\t\t\treturn false\n\t\t}\n\n\t\tif edge.key.from == r.source {\n\t\t\tif amt > r.localAvailable(key) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\tif reserved >= edge.capacity ||\n\t\t\tamt > edge.capacity-reserved {\n\n\t\t\treturn false\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) pathLimit(path []*candidateEdge,\n\tmaxAmount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := maxAmount\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\trt, err := r.buildRoute(mid, path)\n\t\tif err == nil && r.routeFeasible(rt) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc (r *candidateRouter) pathProbability(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil || !r.routeFeasible(rt) {\n\t\treturn 0\n\t}\n\n\tprobability := 1.0\n\tfor i, edge := range path {\n\t\tprobability *= r.probability(edge, routeAmount(rt, i))\n\t}\n\n\treturn probability\n}\n\nfunc (r *candidateRouter) safePathLimit(path []*candidateEdge,\n\thardLimit lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif hardLimit <= 0 {\n\t\treturn 0\n\t}\n\n\thops := len(path)\n\tthreshold := 0.10\n\tif hops <= 2 {\n\t\tthreshold = 0.16\n\t} else if hops >= 5 {\n\t\tthreshold = 0.055\n\t}\n\n\tif r.pathProbability(path, hardLimit) >= threshold {\n\t\treturn hardLimit\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := hardLimit\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathProbability(path, mid) >= threshold {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc pathID(path []*candidateEdge) string {\n\tid := \"\"\n\tfor _, edge := range path {\n\t\tid += fmt.Sprintf(\n\t\t\t\"%d:%x:%x/\", edge.key.chanID, edge.key.from,\n\t\t\tedge.key.to,\n\t\t)\n\t}\n\treturn id\n}\n\nfunc appendUniqueAmount(values []lnwire.MilliSatoshi,\n\tvalue, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value <= 0 || value > maximum {\n\t\treturn values\n\t}\n\tfor _, existing := range values {\n\t\tif existing == value {\n\t\t\treturn values\n\t\t}\n\t}\n\treturn append(values, value)\n}\n\nfunc (r *candidateRouter) probeAmounts(remaining lnwire.MilliSatoshi,\n\tparts uint32) []lnwire.MilliSatoshi {\n\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\n\taverage := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(parts),\n\t)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = appendUniqueAmount(probes, average, remaining)\n\tprobes = appendUniqueAmount(probes, average*2/3, remaining)\n\tprobes = appendUniqueAmount(probes, average/2, remaining)\n\tprobes = appendUniqueAmount(probes, remaining/4, remaining)\n\tprobes = appendUniqueAmount(probes, 50_000_000, remaining)\n\tprobes = appendUniqueAmount(probes, 5_000_000, remaining)\n\tprobes = appendUniqueAmount(probes, 100_000, remaining)\n\tprobes = appendUniqueAmount(probes, minimumShard, remaining)\n\n\tsort.Slice(probes, func(i, j int) bool {\n\t\treturn probes[i] > probes[j]\n\t})\n\n\treturn probes\n}\n\nfunc (r *candidateRouter) planPaths(remaining lnwire.MilliSatoshi,\n\tparts uint32) []pathCandidate {\n\n\tcount := int(parts)\n\tif count < 1 {\n\t\tcount = 1\n\t}\n\tif count > maxPlanCorridors {\n\t\tcount = maxPlanCorridors\n\t}\n\n\tprobes := r.probeAmounts(remaining, parts)\n\tdiversity := make(map[candidateEdgeKey]int)\n\tseen := make(map[string]bool)\n\tplans := make([]pathCandidate, 0, count)\n\n\tfor round := 0; round < count*3 && len(plans) < count; round++ {\n\t\tvar (\n\t\t\tbestPath []*candidateEdge\n\t\t\tbestScore = math.Inf(1)\n\t\t)\n\n\t\tfor _, probe := range probes {\n\t\t\tpath, score, err := r.findPath(probe, diversity)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tid := pathID(path)\n\t\t\tif seen[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnormalized := score / math.Max(\n\t\t\t\tfloat64(probe), 1,\n\t\t\t)\n\t\t\tif normalized < bestScore {\n\t\t\t\tbestPath = path\n\t\t\t\tbestScore = normalized\n\t\t\t}\n\t\t}\n\n\t\tif len(bestPath) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tid := pathID(bestPath)\n\t\tseen[id] = true\n\t\tfor _, edge := range bestPath {\n\t\t\tdiversity[edge.key]++\n\t\t}\n\n\t\thardLimit := r.pathLimit(bestPath, remaining)\n\t\tif hardLimit <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tsafeLimit := r.safePathLimit(bestPath, hardLimit)\n\t\tif safeLimit < minimumShard && hardLimit >= minimumShard {\n\t\t\tsafeLimit = minimumShard\n\t\t}\n\n\t\tplans = append(plans, pathCandidate{\n\t\t\tedges: bestPath,\n\t\t\thardLimit: hardLimit,\n\t\t\tsafeLimit: safeLimit,\n\t\t\tscore: bestScore,\n\t\t})\n\t}\n\n\tsort.SliceStable(plans, func(i, j int) bool {\n\t\tpi := r.pathProbability(\n\t\t\tplans[i].edges,\n\t\t\tmaxAmount(minimumShard, plans[i].safeLimit),\n\t\t)\n\t\tpj := r.pathProbability(\n\t\t\tplans[j].edges,\n\t\t\tmaxAmount(minimumShard, plans[j].safeLimit),\n\t\t)\n\n\t\tif math.Abs(pi-pj) > 0.04 {\n\t\t\treturn pi > pj\n\t\t}\n\t\tif plans[i].safeLimit != plans[j].safeLimit {\n\t\t\treturn plans[i].safeLimit > plans[j].safeLimit\n\t\t}\n\t\tif plans[i].hardLimit != plans[j].hardLimit {\n\t\t\treturn plans[i].hardLimit > plans[j].hardLimit\n\t\t}\n\t\treturn plans[i].score < plans[j].score\n\t})\n\n\treturn plans\n}\n\nfunc maxAmount(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc sumPlanLimits(plans []pathCandidate,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\ttotal := lnwire.MilliSatoshi(0)\n\tfor _, plan := range plans {\n\t\tlimit := plan.hardLimit\n\t\tif safe {\n\t\t\tlimit = plan.safeLimit\n\t\t}\n\t\ttotal += limit\n\t}\n\treturn total\n}\n\nfunc plannedAllocation(remaining lnwire.MilliSatoshi,\n\tplans []pathCandidate) lnwire.MilliSatoshi {\n\n\tif len(plans) == 0 {\n\t\treturn 0\n\t}\n\n\tuseSafe := sumPlanLimits(plans, true) >= remaining\n\tlimit := plans[0].hardLimit\n\tif useSafe {\n\t\tlimit = plans[0].safeLimit\n\t}\n\tif limit <= 0 {\n\t\treturn 0\n\t}\n\n\tif len(plans) == 1 {\n\t\tif limit > remaining {\n\t\t\treturn remaining\n\t\t}\n\t\treturn limit\n\t}\n\n\ttotal := lnwire.MilliSatoshi(0)\n\tother := lnwire.MilliSatoshi(0)\n\tfor i, plan := range plans {\n\t\tvalue := plan.hardLimit\n\t\tif useSafe {\n\t\t\tvalue = plan.safeLimit\n\t\t}\n\t\ttotal += value\n\t\tif i != 0 {\n\t\t\tother += value\n\t\t}\n\t}\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\tallocation := lnwire.MilliSatoshi(\n\t\tfloat64(remaining) * float64(limit) / float64(total),\n\t)\n\n\trequiredNow := subtractFloor(remaining, other)\n\tif allocation < requiredNow {\n\t\tallocation = requiredNow\n\t}\n\n\tfair := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(len(plans)),\n\t)\n\tif allocation < fair && limit >= fair {\n\t\tallocation = fair\n\t}\n\n\tif allocation > limit {\n\t\tallocation = limit\n\t}\n\tif allocation > remaining {\n\t\tallocation = remaining\n\t}\n\tif allocation < minimumShard && remaining >= minimumShard &&\n\t\tlimit >= minimumShard {\n\n\t\tallocation = minimumShard\n\t}\n\n\treturn allocation\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tplans := r.planPaths(amt, partsLeft)\n\n\tif len(plans) != 0 {\n\t\tshard := plannedAllocation(amt, plans)\n\t\tif r.retryHint != 0 && shard > r.retryHint {\n\t\t\tshard = r.retryHint\n\t\t}\n\t\tif shard > plans[0].hardLimit {\n\t\t\tshard = plans[0].hardLimit\n\t\t}\n\t\tif shard > amt {\n\t\t\tshard = amt\n\t\t}\n\n\t\tfor shard > 0 {\n\t\t\trt, err := r.buildRoute(shard, plans[0].edges)\n\t\t\tif err == nil && r.routeFeasible(rt) {\n\t\t\t\tr.reserve(rt)\n\t\t\t\treturn rt, nil\n\t\t\t}\n\n\t\t\tnext := shard * 2 / 3\n\t\t\tif next >= shard {\n\t\t\t\tnext = shard - 1\n\t\t\t}\n\t\t\tif next < minimumShard && amt >= minimumShard {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tshard = next\n\t\t}\n\t}\n\n\tvar candidates []lnwire.MilliSatoshi\n\tcandidates = appendUniqueAmount(candidates, r.retryHint, amt)\n\tcandidates = appendUniqueAmount(candidates, amt, amt)\n\tcandidates = appendUniqueAmount(\n\t\tcandidates,\n\t\tceilDiv(amt, lnwire.MilliSatoshi(partsLeft)),\n\t\tamt,\n\t)\n\tcandidates = appendUniqueAmount(candidates, amt*2/3, amt)\n\tcandidates = appendUniqueAmount(candidates, amt/2, amt)\n\tcandidates = appendUniqueAmount(candidates, amt/3, amt)\n\tcandidates = appendUniqueAmount(candidates, amt/4, amt)\n\tcandidates = appendUniqueAmount(candidates, minimumShard, amt)\n\n\tvar lastErr error\n\tfor _, shard := range candidates {\n\t\tpath, _, err := r.findPath(\n\t\t\tshard, make(map[candidateEdgeKey]int),\n\t\t)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\trt, err := r.buildRoute(shard, path)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\t\tif !r.routeFeasible(rt) {\n\t\t\tlastErr = errors.New(\"route is not feasible\")\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserve(rt)\n\t\treturn rt, nil\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\testimate := belief.estimate\n\tif estimate < amt {\n\t\testimate = amt\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\tif inferred > estimate {\n\t\testimate = inferred\n\t}\n\n\tbelief.estimate = subtractFloor(estimate, amt)\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tif belief.upperBad != 0 {\n\t\tbelief.upperBad = subtractFloor(belief.upperBad, amt)\n\t\tif belief.upperBad == 0 {\n\t\t\tbelief.upperBad = 1\n\t\t}\n\t}\n\n\tlikelyRemaining := lnwire.MilliSatoshi(0)\n\tif edge.capacity > amt {\n\t\tlikelyRemaining = (edge.capacity - amt) * 2 / 3\n\t}\n\tif belief.estimate < likelyRemaining {\n\t\tbelief.estimate = likelyRemaining\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\toutgoing := i + 1\n\t\tif outgoing < len(rt.Hops) {\n\t\t\treturn outgoing\n\t\t}\n\t\treturn -1\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.35\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > minimumShard {\n\t\t\tr.retryHint = delivered * 2 / 3\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.recordPass(key, routeAmount(rt, i))\n\t\tr.routePenalty[key] *= 0.70\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(key, routeAmount(rt, failIndex))\n\t\tr.routePenalty[key] += riskCostMsat * 1.6\n\n\tdefault:\n\t\tr.routePenalty[key] += riskCostMsat * 0.75\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered > minimumShard {\n\t\tretry := delivered * 2 / 3\n\t\tif r.consecutiveFailures >= 3 {\n\t\t\tretry = delivered / 2\n\t\t}\n\t\tif r.consecutiveFailures >= 6 {\n\t\t\tretry = delivered / 3\n\t\t}\n\t\tif retry < minimumShard {\n\t\t\tretry = minimumShard\n\t\t}\n\t\tr.retryHint = retry\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 30,
"parent": 5,
"score": 0.6421,
"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\tfinalCltvDelta = 40\n\tmaxAttempts = 64\n\tminShardAmount = lnwire.MilliSatoshi(1_000)\n\tmaxPlanChoices = 14\n\tplanningDepth = 4\n\tuncoveredWeight = 18.0\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 <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[edgeKey]liquidityBelief\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tpaymentUpper map[edgeKey]lnwire.MilliSatoshi\n\tfailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpaymentUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tfailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tnodes := []route.Vertex{source}\n\n\tfor len(nodes) > 0 {\n\t\tnode := nodes[0]\n\t\tnodes = nodes[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\tnodes = append(nodes, 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.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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, exists := sharedBeliefs.values[key]\n\t\tif !exists {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-20*x)\n\thighMode := 0.48 / (1 + math.Exp(24*(x-0.87)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := math.Max(1, float64(edge.capacity)*0.075)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.84, 0.18*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\nfunc (r *candidateRouter) freshlyImpossible(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\tupper := r.paymentUpper[edge.key]\n\treturn upper > 0 && amt+r.reserved[edge.key] >= upper\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tutility float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, exists := score[item.node]\n\t\tif !exists || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif r.freshlyImpossible(edge, item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.amt + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\t// Reliability dominates. The failure term encourages genuine\n\t\t\t// route diversity while still permitting lower-amount retries.\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/4_000_000 +\n\t\t\t\t0.010 +\n\t\t\t\t0.32*float64(r.failures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < minShardAmount {\n\t\tminimum = minShardAmount\n\t}\n\tif minimum > amt {\n\t\tminimum = amt\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, maxPlanChoices+8)\n\n\tfor _, fraction := range [][2]int64{\n\t\t{1, 1},\n\t\t{9, 10},\n\t\t{4, 5},\n\t\t{3, 4},\n\t\t{2, 3},\n\t\t{3, 5},\n\t\t{1, 2},\n\t\t{2, 5},\n\t\t{1, 3},\n\t} {\n\t\taddAmount(\n\t\t\t&values, seen,\n\t\t\tamt*lnwire.MilliSatoshi(fraction[0])/\n\t\t\t\tlnwire.MilliSatoshi(fraction[1]),\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\taddAmount(&values, seen, minimum*2, minimum, amt)\n\taddAmount(&values, seen, minimum*3/2, minimum, amt)\n\taddAmount(&values, seen, minimum*5/4, minimum, amt)\n\taddAmount(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\tfor _, fraction := range [][2]int64{\n\t\t\t{9, 10},\n\t\t\t{4, 5},\n\t\t\t{2, 3},\n\t\t\t{1, 2},\n\t\t\t{1, 3},\n\t\t} {\n\t\t\taddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\tr.lastFailedAmt*\n\t\t\t\t\tlnwire.MilliSatoshi(fraction[0])/\n\t\t\t\t\tlnwire.MilliSatoshi(fraction[1]),\n\t\t\t\tminimum, amt,\n\t\t\t)\n\t\t}\n\t}\n\n\tvar evidence []lnwire.MilliSatoshi\n\tfor key, upper := range r.paymentUpper {\n\t\tif r.broken[key] || upper <= 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tevidence = append(\n\t\t\tevidence,\n\t\t\tupper*9/10,\n\t\t\tupper*4/5,\n\t\t\tupper*2/3,\n\t\t\tupper/2,\n\t\t)\n\t}\n\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\t\tif belief.lowerOK > 0 {\n\t\t\tevidence = append(\n\t\t\t\tevidence,\n\t\t\t\tbelief.lowerOK,\n\t\t\t\tbelief.lowerOK*9/10,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > 1 {\n\t\t\tevidence = append(\n\t\t\t\tevidence,\n\t\t\t\tbelief.upperFail*4/5,\n\t\t\t\tbelief.upperFail*2/3,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(evidence, func(i, j int) bool {\n\t\treturn evidence[i] > evidence[j]\n\t})\n\tif len(evidence) > 10 {\n\t\tevidence = evidence[:10]\n\t}\n\n\tfor _, value := range evidence {\n\t\taddAmount(&values, seen, value, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\tif len(values) > maxPlanChoices {\n\t\tvalues = values[:maxPlanChoices]\n\t}\n\n\treturn values\n}\n\nfunc (r *candidateRouter) reserveChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\tr.reserved[key] += choice.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) releaseChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= choice.amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - choice.amounts[i]\n\t\t}\n\t}\n}\n\nfunc choiceCost(choice *routeChoice) float64 {\n\tretryCost := 1/choice.probability - 1\n\tif retryCost > 12 {\n\t\tretryCost = 12\n\t}\n\n\treturn -math.Log(choice.probability) +\n\t\t0.16*retryCost +\n\t\tfloat64(choice.fee)/4_000_000 +\n\t\t0.20\n}\n\n// planRemainder performs a bounded route-set search. Reservations make each\n// successive route account for the liquidity already assigned to earlier\n// shards, which naturally produces unequal allocations across independent\n// corridors.\nfunc (r *candidateRouter) planRemainder(\n\tremaining lnwire.MilliSatoshi, slots, depth uint32) float64 {\n\n\tif remaining <= 0 {\n\t\treturn 0\n\t}\n\tif slots == 0 {\n\t\treturn uncoveredWeight\n\t}\n\n\tif depth == 0 {\n\t\tchoice, err := r.findRoute(remaining)\n\t\tif err == nil {\n\t\t\treturn choiceCost(choice)\n\t\t}\n\n\t\treturn uncoveredWeight +\n\t\t\t4*float64(remaining)/float64(remaining+1_000_000)\n\t}\n\n\tamounts := r.candidateAmounts(remaining, slots)\n\tbest := math.Inf(1)\n\tlimit := len(amounts)\n\tif limit > 8 {\n\t\tlimit = 8\n\t}\n\n\tfor _, shard := range amounts[:limit] {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserveChoice(choice)\n\t\trest := r.planRemainder(\n\t\t\tremaining-shard, slots-1, depth-1,\n\t\t)\n\t\tr.releaseChoice(choice)\n\n\t\t// Reward progress per occupied part. This prevents a sequence of\n\t\t// extremely reliable dust shards from consuming the attempt budget.\n\t\tprogress := float64(shard) / float64(remaining)\n\t\tcost := choiceCost(choice) + rest + 0.34*(1-progress)\n\t\tif cost < best {\n\t\t\tbest = cost\n\t\t}\n\t}\n\n\tif math.IsInf(best, 1) {\n\t\treturn uncoveredWeight\n\t}\n\n\treturn best\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tamounts := r.candidateAmounts(amt, partsLeft)\n\n\tvar best *routeChoice\n\tlimit := len(amounts)\n\tif limit > maxPlanChoices {\n\t\tlimit = maxPlanChoices\n\t}\n\n\tfor _, shard := range amounts[:limit] {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserveChoice(choice)\n\t\tdepth := uint32(planningDepth)\n\t\tif partsLeft-1 < depth {\n\t\t\tdepth = partsLeft - 1\n\t\t}\n\t\tcontinuation := r.planRemainder(\n\t\t\tamt-shard, partsLeft-1, depth,\n\t\t)\n\t\tr.releaseChoice(choice)\n\n\t\tprogress := float64(shard) / float64(amt)\n\t\tchoice.utility = choiceCost(choice) +\n\t\t\tcontinuation +\n\t\t\t0.42*(1-progress)\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tr.reserveChoice(best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tfresh := r.paymentUpper[key]\n\tif fresh == 0 || amt < fresh {\n\t\tr.paymentUpper[key] = amt\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tlowEstimate := amt / 5\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = lowEstimate\n\t} else {\n\t\tbelief.estimate = (3*belief.estimate + lowEstimate) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnProbeSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\n\toptimistic := edge.capacity * 4 / 5\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= belief.lowerOK {\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tif belief.upperFail > 0 &&\n\t\tbelief.lowerOK >= belief.upperFail {\n\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseRoute(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[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 || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseRoute(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.failures[key] > 0 {\n\t\t\t\tr.failures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\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\t// A forwarding node reports a failure for its outgoing edge. The\n\t\t// source of hop i therefore corresponds to edge i+1.\n\t\tfor i := 0; i+1 < len(rt.Hops); i++ {\n\t\t\tif rt.Hops[i].PubKeyBytes ==\n\t\t\t\tresult.FailureSource {\n\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tr.failures[key]++\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnProbeSuccess(keys[i], amounts[i])\n\t\tif r.failures[keys[i]] > 0 {\n\t\t\tr.failures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.failures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 31,
"parent": 8,
"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 (\n\tfinalCltvDelta = 40\n\tmaxAttempts = 64\n\tminShardAmount = lnwire.MilliSatoshi(1_000)\n\n\tmaxAmountChoices = 18\n\tmaxRouteChoices = 4\n\tplanningDepth = 3\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\n\tcapacity lnwire.MilliSatoshi\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[edgeKey]liquidityBelief\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tpaymentUpper map[edgeKey]lnwire.MilliSatoshi\n\tfailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpaymentUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tfailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tnodes := []route.Vertex{source}\n\n\tfor len(nodes) > 0 {\n\t\tnode := nodes[0]\n\t\tnodes = nodes[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\tnodes = append(nodes, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\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\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-20*x)\n\thighMode := 0.48 / (1 + math.Exp(24*(x-0.87)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\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\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := math.Max(1, float64(edge.capacity)*0.07)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.86, 0.18*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\nfunc (r *candidateRouter) freshlyImpossible(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\tupper := r.paymentUpper[edge.key]\n\treturn upper > 0 && amt+r.reserved[edge.key] >= upper\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tcost float64\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\tdiversity map[edgeKey]float64) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif 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.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif r.freshlyImpossible(edge, item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.amt + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/5_000_000 +\n\t\t\t\t0.008 +\n\t\t\t\t0.22*float64(r.failures[edge.key]) +\n\t\t\t\tdiversity[edge.key]\n\n\t\t\tcandidate := item.score + step\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: 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\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\tchoice := &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}\n\tchoice.cost = choiceCost(choice)\n\n\treturn choice, nil\n}\n\nfunc (r *candidateRouter) routeChoices(\n\tdeliver lnwire.MilliSatoshi) []*routeChoice {\n\n\tvar choices []*routeChoice\n\tdiversity := make(map[edgeKey]float64)\n\tsignatures := make(map[string]bool)\n\n\tfor i := 0; i < maxRouteChoices; i++ {\n\t\tchoice, err := r.findRoute(deliver, diversity)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tsignature := \"\"\n\t\tfor _, key := range choice.keys {\n\t\t\tsignature += fmt.Sprintf(\n\t\t\t\t\"%d:%x:%x/\", key.chanID, key.from[:4], key.to[:4],\n\t\t\t)\n\t\t}\n\t\tif !signatures[signature] {\n\t\t\tsignatures[signature] = true\n\t\t\tchoices = append(choices, choice)\n\t\t}\n\n\t\tfor _, key := range choice.keys {\n\t\t\tdiversity[key] += 0.85\n\t\t}\n\t}\n\n\treturn choices\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\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}, keys, amounts, nil\n}\n\nfunc addAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minShardAmount {\n\t\tvalue = minShardAmount\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) edgeOffer(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := edge.capacity - r.reserved[edge.key]\n\tif available <= 0 {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance := r.localBalances[edge.key.chanID] -\n\t\t\tr.reserved[edge.key]\n\t\tif balance < available {\n\t\t\tavailable = balance\n\t\t}\n\n\t\treturn available\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tswitch {\n\tcase belief.lowerOK > r.reserved[edge.key]:\n\t\tavailable = belief.lowerOK - r.reserved[edge.key]\n\n\tcase belief.upperFail > r.reserved[edge.key]+1:\n\t\tavailable = belief.upperFail -\n\t\t\tr.reserved[edge.key] - 1\n\n\tcase belief.estimate > r.reserved[edge.key]:\n\t\tavailable = belief.estimate - r.reserved[edge.key]\n\n\tdefault:\n\t\tavailable = edge.capacity * 4 / 5\n\t}\n\n\tif upper := r.paymentUpper[edge.key]; upper > 0 {\n\t\tif upper <= r.reserved[edge.key]+1 {\n\t\t\treturn 0\n\t\t}\n\n\t\tfresh := upper - r.reserved[edge.key] - 1\n\t\tif fresh < available {\n\t\t\tavailable = fresh\n\t\t}\n\t}\n\n\tif edge.maxHTLC > 0 && available > edge.maxHTLC {\n\t\tavailable = edge.maxHTLC\n\t}\n\n\treturn available\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, maxAmountChoices+16)\n\n\tequal := (amt + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tamt,\n\t\tamt * 9 / 10,\n\t\tamt * 4 / 5,\n\t\tamt * 3 / 4,\n\t\tamt * 2 / 3,\n\t\tamt * 3 / 5,\n\t\tamt / 2,\n\t\tamt * 2 / 5,\n\t\tamt / 3,\n\t\tequal * 3 / 2,\n\t\tequal * 5 / 4,\n\t\tequal,\n\t\tequal * 4 / 5,\n\t\tequal * 2 / 3,\n\t} {\n\t\taddAmount(&values, seen, value, amt)\n\t}\n\n\tif r.lastFailedAmt > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailedAmt * 9 / 10,\n\t\t\tr.lastFailedAmt * 4 / 5,\n\t\t\tr.lastFailedAmt * 2 / 3,\n\t\t\tr.lastFailedAmt / 2,\n\t\t\tr.lastFailedAmt / 3,\n\t\t} {\n\t\t\taddAmount(&values, seen, value, amt)\n\t\t}\n\t}\n\n\tvar offers []lnwire.MilliSatoshi\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tif r.broken[edge.key] {\n\t\t\tcontinue\n\t\t}\n\n\t\toffer := r.edgeOffer(edge)\n\t\tif offer > 0 {\n\t\t\toffers = append(offers, offer)\n\t\t}\n\t}\n\n\tfor key, upper := range r.paymentUpper {\n\t\tif r.broken[key] || upper <= r.reserved[key]+1 {\n\t\t\tcontinue\n\t\t}\n\n\t\toffers = append(\n\t\t\toffers,\n\t\t\tupper-r.reserved[key]-1,\n\t\t)\n\t}\n\n\tsort.Slice(offers, func(i, j int) bool {\n\t\treturn offers[i] > offers[j]\n\t})\n\tif len(offers) > 12 {\n\t\toffers = offers[:12]\n\t}\n\n\tfor _, offer := range offers {\n\t\taddAmount(&values, seen, offer, amt)\n\t\taddAmount(&values, seen, offer*9/10, amt)\n\t\taddAmount(&values, seen, offer*4/5, amt)\n\t\taddAmount(&values, seen, offer*2/3, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\tif len(values) > maxAmountChoices {\n\t\tkept := append(\n\t\t\t[]lnwire.MilliSatoshi(nil),\n\t\t\tvalues[:maxAmountChoices-4]...,\n\t\t)\n\t\ttailStart := len(values) - 4\n\t\tif tailStart < maxAmountChoices-4 {\n\t\t\ttailStart = maxAmountChoices - 4\n\t\t}\n\t\tfor _, value := range values[tailStart:] {\n\t\t\tduplicate := false\n\t\t\tfor _, old := range kept {\n\t\t\t\tif old == value {\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\tkept = append(kept, value)\n\t\t\t}\n\t\t}\n\t\tvalues = kept\n\t}\n\n\treturn values\n}\n\nfunc choiceCost(choice *routeChoice) float64 {\n\tretryCost := 1/choice.probability - 1\n\tif retryCost > 15 {\n\t\tretryCost = 15\n\t}\n\n\treturn -math.Log(choice.probability) +\n\t\t0.14*retryCost +\n\t\tfloat64(choice.fee)/5_000_000 +\n\t\t0.12\n}\n\nfunc (r *candidateRouter) reserveChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\tr.reserved[key] += choice.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) releaseChoice(choice *routeChoice) {\n\tfor i, key := range choice.keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= choice.amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - choice.amounts[i]\n\t\t}\n\t}\n}\n\ntype planResult struct {\n\tcost float64\n\tcovered lnwire.MilliSatoshi\n}\n\nfunc betterPlan(a, b planResult) bool {\n\tif a.covered != b.covered {\n\t\treturn a.covered > b.covered\n\t}\n\n\treturn a.cost < b.cost\n}\n\nfunc (r *candidateRouter) planRemainder(remaining lnwire.MilliSatoshi,\n\tslots, depth uint32) planResult {\n\n\tif remaining <= 0 {\n\t\treturn planResult{}\n\t}\n\tif slots == 0 {\n\t\treturn planResult{\n\t\t\tcost: math.Inf(1),\n\t\t}\n\t}\n\n\tif slots == 1 || depth == 0 {\n\t\tchoices := r.routeChoices(remaining)\n\t\tif len(choices) == 0 {\n\t\t\treturn planResult{\n\t\t\t\tcost: math.Inf(1),\n\t\t\t}\n\t\t}\n\n\t\treturn planResult{\n\t\t\tcost: choices[0].cost,\n\t\t\tcovered: remaining,\n\t\t}\n\t}\n\n\tbest := planResult{\n\t\tcost: math.Inf(1),\n\t}\n\n\tamounts := r.candidateAmounts(remaining, slots)\n\tlimit := len(amounts)\n\tif limit > 11 {\n\t\tlimit = 11\n\t}\n\n\tfor _, shard := range amounts[:limit] {\n\t\tchoices := r.routeChoices(shard)\n\t\tfor _, choice := range choices {\n\t\t\tr.reserveChoice(choice)\n\t\t\trest := r.planRemainder(\n\t\t\t\tremaining-shard, slots-1, depth-1,\n\t\t\t)\n\t\t\tr.releaseChoice(choice)\n\n\t\t\tcovered := shard + rest.covered\n\t\t\tcost := choice.cost + rest.cost\n\t\t\tif math.IsInf(rest.cost, 1) {\n\t\t\t\tcost = choice.cost +\n\t\t\t\t\t20*float64(remaining-covered)/\n\t\t\t\t\t\tfloat64(remaining)\n\t\t\t}\n\n\t\t\tprogress := float64(shard) / float64(remaining)\n\t\t\tcandidate := planResult{\n\t\t\t\tcost: cost + 0.18*(1-progress),\n\t\t\t\tcovered: covered,\n\t\t\t}\n\t\t\tif betterPlan(candidate, best) {\n\t\t\t\tbest = candidate\n\t\t\t}\n\t\t}\n\t}\n\n\treturn best\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tslots := r.spec.MaxParts - inFlightHtlcs\n\tamounts := r.candidateAmounts(amt, slots)\n\n\tvar best *routeChoice\n\tbestCovered := lnwire.MilliSatoshi(-1)\n\tbestUtility := math.Inf(1)\n\n\tfor _, shard := range amounts {\n\t\tchoices := r.routeChoices(shard)\n\t\tfor _, choice := range choices {\n\t\t\tr.reserveChoice(choice)\n\n\t\t\tdepth := uint32(planningDepth)\n\t\t\tif slots > 0 && slots-1 < depth {\n\t\t\t\tdepth = slots - 1\n\t\t\t}\n\t\t\trest := r.planRemainder(\n\t\t\t\tamt-shard, slots-1, depth,\n\t\t\t)\n\n\t\t\tr.releaseChoice(choice)\n\n\t\t\tcovered := shard + rest.covered\n\t\t\tutility := choice.cost + rest.cost\n\t\t\tif math.IsInf(rest.cost, 1) {\n\t\t\t\tmissing := amt - covered\n\t\t\t\tutility = choice.cost +\n\t\t\t\t\t25*float64(missing)/float64(amt)\n\t\t\t}\n\n\t\t\tprogress := float64(shard) / float64(amt)\n\t\t\tutility += 0.20 * (1 - progress)\n\n\t\t\tif covered > bestCovered ||\n\t\t\t\t(covered == bestCovered &&\n\t\t\t\t\tutility < bestUtility) {\n\n\t\t\t\tbest = choice\n\t\t\t\tbestCovered = covered\n\t\t\t\tbestUtility = utility\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tr.reserveChoice(best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\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{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\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\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tfresh := r.paymentUpper[key]\n\tif fresh == 0 || amt < fresh {\n\t\tr.paymentUpper[key] = amt\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tlowEstimate := amt / 6\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = lowEstimate\n\t} else {\n\t\tbelief.estimate = (3*belief.estimate + lowEstimate) / 4\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnProbeSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\n\toptimistic := edge.capacity * 4 / 5\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\n\tif belief.upperFail > 0 &&\n\t\tbelief.upperFail <= belief.lowerOK {\n\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\testimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif estimate < optimistic {\n\t\testimate = (estimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(estimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tif belief.upperFail > 0 &&\n\t\tbelief.lowerOK >= belief.upperFail {\n\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseRoute(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[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 || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseRoute(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.failures[key] > 0 {\n\t\t\t\tr.failures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\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 := 0; i+1 < len(rt.Hops); i++ {\n\t\t\tif rt.Hops[i].PubKeyBytes ==\n\t\t\t\tresult.FailureSource {\n\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tr.failures[key]++\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnProbeSuccess(keys[i], amounts[i])\n\t\tif r.failures[keys[i]] > 0 {\n\t\t\tr.failures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.failures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
}
}
]
}