lnd/simulation/command-center/data/run.json
Olaoluwa Osuntokun 445e8fb891 command-center: point run telemetry at code_econ2
In this commit, we swap the telemetry panel's lineage to the
economic-world run whose winner the new sections discuss.
2026-07-29 01:13:36 -07:00

648 lines
No EOL
1 MiB

{
"run_id": "code_econ2",
"reflection_lm": "codex:gpt-5.6-sol",
"mode": "generalization",
"status": "complete",
"seed_score": 0.295,
"best_score": 0.8797,
"iterations": [
{
"i": 0,
"candidate_score": 0.295,
"best_score": 0.295,
"note": "seed"
},
{
"i": 1,
"candidate_score": 0.4244,
"best_score": 0.295,
"note": "rejected"
},
{
"i": 2,
"candidate_score": 0.1255,
"best_score": 0.295,
"note": "rejected"
},
{
"i": 3,
"candidate_score": 0.1881,
"best_score": 0.295,
"note": "rejected"
},
{
"i": 4,
"candidate_score": 0.1231,
"best_score": 0.295,
"note": "rejected"
},
{
"i": 5,
"candidate_score": 0.0809,
"best_score": 0.295,
"note": "rejected"
},
{
"i": 6,
"candidate_score": 0.0,
"best_score": 0.295,
"note": "rejected"
},
{
"i": 7,
"candidate_score": 0.3036,
"best_score": 0.3036,
"note": "accepted"
},
{
"i": 8,
"candidate_score": 0.3858,
"best_score": 0.3036,
"note": "rejected"
},
{
"i": 9,
"candidate_score": 0.2035,
"best_score": 0.3036,
"note": "rejected"
},
{
"i": 10,
"candidate_score": 0.1405,
"best_score": 0.3036,
"note": "rejected"
},
{
"i": 11,
"candidate_score": 0.3981,
"best_score": 0.3036,
"note": "rejected"
},
{
"i": 12,
"candidate_score": 0.3453,
"best_score": 0.3036,
"note": "rejected"
},
{
"i": 13,
"candidate_score": 0.1969,
"best_score": 0.3036,
"note": "accepted"
},
{
"i": 14,
"candidate_score": 0.3308,
"best_score": 0.3308,
"note": "accepted"
},
{
"i": 15,
"candidate_score": 0.4705,
"best_score": 0.4705,
"note": "accepted"
},
{
"i": 16,
"candidate_score": 0.1838,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 17,
"candidate_score": 0.346,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 18,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 19,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 20,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 21,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 22,
"candidate_score": 0.1349,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 23,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 24,
"candidate_score": 0.1672,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 25,
"candidate_score": 0.1296,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 26,
"candidate_score": 0.3656,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 27,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 28,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 29,
"candidate_score": 0.3872,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 30,
"candidate_score": 0.0,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 31,
"candidate_score": 0.2183,
"best_score": 0.4705,
"note": "accepted"
},
{
"i": 32,
"candidate_score": 0.3885,
"best_score": 0.4705,
"note": "accepted"
},
{
"i": 33,
"candidate_score": 0.3204,
"best_score": 0.4705,
"note": "accepted"
},
{
"i": 34,
"candidate_score": 0.2579,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 35,
"candidate_score": 0.2235,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 36,
"candidate_score": 0.2197,
"best_score": 0.4705,
"note": "rejected"
},
{
"i": 37,
"candidate_score": 0.1076,
"best_score": 0.4705,
"note": "accepted"
},
{
"i": 38,
"candidate_score": 0.45,
"best_score": 0.4705,
"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\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(30_000)\n\n\thopCostMsat = float64(5_000)\n\treusedEdgeCostMsat = float64(1_100_000)\n\textraPartCostMsat = float64(45_000)\n\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 8\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 40\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// reserved is cumulative load contributed by successful shards in this\n\t// payment. It models both held atomic shards and liquidity consumed by\n\t// already-settled non-atomic shards.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\t// localUpper is deliberately payment-local and stricter than the shared\n\t// belief. A failed load must be retried substantially lower, rather than\n\t// being sent repeatedly at nearly the same amount.\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\t// Shared failures remain soft because another payment or held shard\n\t\t// may have caused a transient depletion.\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\n\treturn price\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tcutoff := upper * 3 / 4\n\tif r.localFails[key] >= 3 {\n\t\tcutoff = upper / 2\n\t}\n\tif cutoff <= 0 {\n\t\tcutoff = 1\n\t}\n\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\n\t\t\tif edge.from == r.source {\n\t\t\t\t// The snapshot makes the first hop nearly certain until an\n\t\t\t\t// observed local or concurrent reservation failure says\n\t\t\t\t// otherwise.\n\t\t\t\tp = 0.995\n\t\t\t\tif r.localUpper[edge.key] > 0 {\n\t\t\t\t\tp = 0.80\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.55\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tint64(total) * weight / sum,\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - 1\n\t\t}\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{40, 60}, {60, 40},\n\t\t\t{25, 75}, {75, 25},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{20, 30, 50},\n\t\t\t{50, 30, 20},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 4:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{15, 20, 25, 40},\n\t\t\t{40, 25, 20, 15},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tif parts > 4 {\n\t\t\tascending := make([]int64, parts)\n\t\t\tdescending := make([]int64, parts)\n\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tascending[i] = int64(i + 1)\n\t\t\t\tdescending[i] = int64(parts - i)\n\t\t\t}\n\n\t\t\tpatterns = append(\n\t\t\t\tpatterns,\n\t\t\t\tweightedAllocation(total, ascending),\n\t\t\t\tweightedAllocation(total, descending),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tshardCap := remainingBudget\n\n\t\t// Reserve a proportional share of a finite budget for later\n\t\t// shards. A small allowance avoids rejecting a shard merely due\n\t\t// to base-fee rounding.\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\tremainingAmount > shard {\n\n\t\t\tshardCap = remainingBudget * shard / remainingAmount\n\t\t\tallowance := remainingBudget / 20\n\t\t\tif allowance > 25_000 {\n\t\t\t\tallowance = 25_000\n\t\t\t}\n\t\t\tif shardCap <= remainingBudget-allowance {\n\t\t\t\tshardCap += allowance\n\t\t\t} else {\n\t\t\t\tshardCap = remainingBudget\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\t// When the whole route set cannot be formed, make one useful shard.\n\t// Each failure bound forces the next attempt onto another corridor or\n\t// onto a materially smaller amount.\n\tif slots > 1 {\n\t\tshard := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tfor shard > 0 {\n\t\t\tcap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tcap = feeBudget * shard / total\n\t\t\t\tif cap < 1_000 {\n\t\t\t\t\tcap = 1_000\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\n\t\t\tif shard <= 10_000 {\n\t\t\t\tbreak\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\tshard = next\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\t// MaxParts limits concurrent shards, not the lifetime number of\n\t// successful shards. Atomic held shards appear in inFlightHtlcs,\n\t// while settled non-atomic shards release their slots.\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t// A forwarding node reports failure of its outgoing edge.\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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.30\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\t// A source-side liquidity failure may be represented without a public\n\t// failure source. It is safe and useful to attribute that case to the\n\t// first channel; treating it as an uninformative dispatch refusal causes\n\t// an otherwise identical route to be retried until the runner times out.\n\tif failIdx < 0 &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure &&\n\t\tlen(meta.path) > 0 {\n\n\t\tfailIdx = 0\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t300_000 + float64(count)*175_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\t// Gossip-policy mismatches are deterministic within this\n\t\t\t// payment and should never poison liquidity estimates.\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_000_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// An unidentified non-liquidity failure provides no trustworthy\n\t// channel bound. Penalize the whole route to obtain diversity. If a\n\t// finite budget was involved, retain a small guard against dispatch\n\t// accounting or rounding differences.\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 500_000\n\t}\n\n\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\tguard := meta.fee / 20\n\t\tif guard < 1_000 {\n\t\t\tguard = 1_000\n\t\t}\n\n\t\tremaining := r.remainingFeeBudget()\n\t\tif remaining != lnwire.MaxMilliSatoshi && guard > remaining {\n\t\t\tguard = remaining\n\t\t}\n\t\tr.budgetGuard += guard\n\t}\n\n\treturn nil\n}"
},
"stats": {
"evals_done": 382,
"distinct_candidates": 39
},
"candidates": [
{
"id": 0,
"parent": null,
"score": 0.295,
"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.4244,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskPriceMsat = 420000.0\n\tmaxRouteHops = 30\n\tmaxNodeLabels = 4\n\tmaxAttempts = 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\tinboundBase int32\n\tinboundRate int32\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 {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\tsuccesses uint16\n\tfailures uint16\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\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.5 * math.Exp(-x/0.035)\n\thighMode := 0.5 / (1 + math.Exp((x-0.92)*30))\n\tp := lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc edgeProbability(e *candidateEdge, load lnwire.MilliSatoshi) float64 {\n\tprior := bimodalPrior(load, e.capacity)\n\n\tsharedBeliefs.Lock()\n\tb := sharedBeliefs.m[e.key]\n\tsharedBeliefs.Unlock()\n\n\tif b.lowerOK == 0 && b.upperFail == 0 {\n\t\treturn prior\n\t}\n\tif b.lowerOK > 0 && load <= b.lowerOK {\n\t\treturn 0.985\n\t}\n\tif b.upperFail > 0 && load >= b.upperFail {\n\t\treturn 0.012\n\t}\n\n\tvar evidence float64\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperFail > b.lowerOK:\n\t\tx := float64(load-b.lowerOK) /\n\t\t\tfloat64(b.upperFail-b.lowerOK)\n\t\tevidence = 0.985 - x*(0.985-0.012)\n\n\tcase b.upperFail > 0:\n\t\tx := float64(load) / float64(b.upperFail)\n\t\tevidence = 0.012 + 0.973*math.Exp(-x/0.20)\n\n\tcase b.lowerOK > 0:\n\t\texcess := float64(load-b.lowerOK) / float64(e.capacity)\n\t\tevidence = 0.985 * math.Exp(-5*excess)\n\n\tdefault:\n\t\tevidence = prior\n\t}\n\n\tconfidence := float64(b.successes+b.failures) * 0.18\n\tif confidence > 0.82 {\n\t\tconfidence = 0.82\n\t}\n\tp := prior*(1-confidence) + evidence*confidence\n\tif p < 0.012 {\n\t\treturn 0.012\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc recordSuccess(key edgeKey, load lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tif b.upperFail != 0 && load >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\tif b.successes < 100 {\n\t\tb.successes++\n\t}\n\tsharedBeliefs.m[key] = b\n}\n\nfunc recordFailure(key edgeKey, load lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif b.lowerOK >= load {\n\t\tb.lowerOK = load * 3 / 4\n\t}\n\tif b.upperFail == 0 || load < b.upperFail {\n\t\tb.upperFail = load\n\t}\n\tif b.failures < 100 {\n\t\tb.failures++\n\t}\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchLabel struct {\n\tnode route.Vertex\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n\tsuffix *searchLabel\n\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tindex int\n}\n\ntype labelQueue []*searchLabel\n\nfunc (q labelQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q labelQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q labelQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *labelQueue) Push(x any) {\n\titem := x.(*searchLabel)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *labelQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype candidatePath struct {\n\trt *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tedges []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype plannedRoute struct {\n\tpath *candidatePath\n}\n\ntype attemptMeta struct {\n\tdeliver lnwire.MilliSatoshi\n\tedges []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeIndex map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tactiveLoads map[edgeKey]lnwire.MilliSatoshi\n\ttempPenalty map[edgeKey]float64\n\tpolicyBad map[edgeKey]bool\n\n\tplanned []*plannedRoute\n\tpending map[*route.Route]*attemptMeta\n\n\tcommittedFees lnwire.MilliSatoshi\n\tpartsUsed uint32\n\tretryCap 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\tedgeIndex: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tactiveLoads: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\ttempPenalty: make(map[edgeKey]float64),\n\t\tpolicyBad: make(map[edgeKey]bool),\n\t\tpending: make(map[*route.Route]*attemptMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := 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\te := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[key.to] = append(\n\t\t\t\t\tr.incomingEdges[key.to], e,\n\t\t\t\t)\n\t\t\t\tr.edgeIndex[key] = e\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc combinedForwardingFee(incoming, outgoing *candidateEdge,\n\toutAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := outgoing.fee(outAmt)\n\tfeeBase := int64(outAmt + outFee)\n\tinFee := int64(incoming.inboundBase) +\n\t\tfeeBase*int64(incoming.inboundRate)/1_000_000\n\n\ttotal := int64(outFee) + inFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\nfunc labelContains(label *searchLabel, node route.Vertex) bool {\n\tfor l := label; l != nil; l = l.suffix {\n\t\tif l.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc acceptLabel(best map[route.Vertex][]float64, node route.Vertex,\n\tscore float64) bool {\n\n\tscores := best[node]\n\tif len(scores) < maxNodeLabels {\n\t\tbest[node] = append(scores, score)\n\t\treturn true\n\t}\n\n\tworst := 0\n\tfor i := 1; i < len(scores); i++ {\n\t\tif scores[i] > scores[worst] {\n\t\t\tworst = i\n\t\t}\n\t}\n\tif score >= scores[worst] {\n\t\treturn false\n\t}\n\tscores[worst] = score\n\tbest[node] = scores\n\treturn true\n}\n\nfunc (r *candidateRouter) remainingBudget() lnwire.MilliSatoshi {\n\tlimit := r.spec.FeeLimitMsat\n\tif limit == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= limit {\n\t\treturn 0\n\t}\n\treturn limit - r.committedFees\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\tused map[edgeKey]int,\n\tmaxFee lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid shard amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tstart := &searchLabel{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t\tscore: 0,\n\t}\n\tpq := &labelQueue{}\n\theap.Push(pq, start)\n\tbest := map[route.Vertex][]float64{\n\t\tr.spec.Target: {0},\n\t}\n\n\tfor pq.Len() != 0 {\n\t\tcur := heap.Pop(pq).(*searchLabel)\n\t\tif cur.node == r.source {\n\t\t\treturn r.buildPath(deliver, cur, reserved)\n\t\t}\n\t\tif cur.hops >= maxRouteHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, e := range r.incomingEdges[cur.node] {\n\t\t\tif r.policyBad[e.key] || labelContains(cur, e.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeAmt := cur.amt\n\t\t\taddedFee := lnwire.MilliSatoshi(0)\n\t\t\tif cur.edge != nil {\n\t\t\t\taddedFee = combinedForwardingFee(\n\t\t\t\t\te, cur.edge, cur.amt,\n\t\t\t\t)\n\t\t\t\tedgeAmt += addedFee\n\t\t\t}\n\t\t\tif !e.usable(edgeAmt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := edgeAmt + reserved[e.key]\n\t\t\tif load > e.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprob := edgeProbability(e, load)\n\t\t\tif e.key.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[e.key.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprob = 0.999\n\t\t\t}\n\n\t\t\tnewFee := cur.fee + addedFee\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi &&\n\t\t\t\tnewFee > maxFee {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tincrement := float64(addedFee) -\n\t\t\t\triskPriceMsat*math.Log(prob)\n\t\t\tincrement += r.tempPenalty[e.key]\n\t\t\tif n := used[e.key]; n > 0 {\n\t\t\t\tincrement += float64(n) * 300000\n\t\t\t}\n\n\t\t\tnext := &searchLabel{\n\t\t\t\tnode: e.key.from,\n\t\t\t\tedge: e,\n\t\t\t\tamt: edgeAmt,\n\t\t\t\tsuffix: cur,\n\t\t\t\tfee: newFee,\n\t\t\t\tscore: cur.score + increment,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t}\n\t\t\tif !acceptLabel(best, next.node, next.score) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\theap.Push(pq, next)\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildPath(deliver lnwire.MilliSatoshi,\n\tsourceLabel *searchLabel,\n\treserved map[edgeKey]lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tvar edges []*candidateEdge\n\tvar amounts []lnwire.MilliSatoshi\n\n\tfor label := sourceLabel; label != nil && label.edge != nil;\n\t\tlabel = label.suffix {\n\n\t\tedges = append(edges, label.edge)\n\t\tamounts = append(amounts, label.amt)\n\t}\n\tif len(edges) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\tif edges[len(edges)-1].key.to != r.spec.Target {\n\t\treturn nil, errors.New(\"route does not reach target\")\n\t}\n\n\texpiry := make([]uint32, len(edges))\n\texpiry[len(expiry)-1] = finalCltvDelta\n\tfor i := len(edges) - 2; i >= 0; i-- {\n\t\texpiry[i] = expiry[i+1] +\n\t\t\tuint32(edges[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(edges))\n\tloads := make([]lnwire.MilliSatoshi, len(edges))\n\tfor i, e := range edges {\n\t\tforward := deliver\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(edges) {\n\t\t\tforward = amounts[i+1]\n\t\t\toutgoingExpiry = expiry[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: e.key.to,\n\t\t\tChannelID: e.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t\tloads[i] = amounts[i] + reserved[e.key]\n\t}\n\n\trt := &route.Route{\n\t\tTotalTimeLock: expiry[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}\n\treturn &candidatePath{\n\t\trt: rt,\n\t\tdeliver: deliver,\n\t\tedges: edges,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tscore: sourceLabel.score,\n\t}, nil\n}\n\nfunc copyLoads(src map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor k, v := range src {\n\t\tdst[k] = v\n\t}\n\treturn dst\n}\n\nfunc copyUsage(src map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]int {\n\tdst := make(map[edgeKey]int, len(src))\n\tfor k, v := range src {\n\t\tif v > 0 {\n\t\t\tdst[k] = 1\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc ceilDiv(a lnwire.MilliSatoshi, b int) lnwire.MilliSatoshi {\n\treturn (a + lnwire.MilliSatoshi(b) - 1) /\n\t\tlnwire.MilliSatoshi(b)\n}\n\nfunc (r *candidateRouter) buildPlanVariant(total lnwire.MilliSatoshi,\n\tparts int, bias int, maxShard lnwire.MilliSatoshi,\n\tbudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treserved := copyLoads(r.activeLoads)\n\tused := copyUsage(r.activeLoads)\n\tremaining := total\n\tremainingBudget := budget\n\tplan := make([]*plannedRoute, 0, parts)\n\ttotalScore := 0.0\n\n\tfor i := 0; i < parts; i++ {\n\t\tleft := parts - i\n\t\tshard := remaining\n\t\tif left > 1 {\n\t\t\tbase := ceilDiv(remaining, left)\n\t\t\tshard = base * lnwire.MilliSatoshi(bias) / 100\n\t\t\tif shard < 1 {\n\t\t\t\tshard = 1\n\t\t\t}\n\n\t\t\tminRemainder := lnwire.MilliSatoshi(left - 1)\n\t\t\tif shard > remaining-minRemainder {\n\t\t\t\tshard = remaining - minRemainder\n\t\t\t}\n\t\t}\n\t\tif maxShard > 0 && shard > maxShard {\n\t\t\tshard = maxShard\n\t\t}\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tpath, err := r.findRoute(\n\t\t\tshard, reserved, used, remainingBudget,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tfee := path.rt.TotalAmount - path.deliver\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= fee\n\t\t}\n\n\t\tplan = append(plan, &plannedRoute{path: path})\n\t\ttotalScore += path.score\n\t\tfor j, e := range path.edges {\n\t\t\treserved[e.key] += path.amounts[j]\n\t\t\tused[e.key]++\n\t\t}\n\t\tremaining -= shard\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, 0, false\n\t}\n\n\ttotalScore += float64(parts-1) * 150000\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) createPlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tbudget := r.remainingBudget()\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\tbiases := []int{125, 100, 80}\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tif r.retryCap > 0 &&\n\t\t\tceilDiv(total, parts) > r.retryCap {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, bias := range biases {\n\t\t\tif parts == 1 && bias != 100 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tplan, score, ok := r.buildPlanVariant(\n\t\t\t\ttotal, parts, bias, r.retryCap, budget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) issuePlanned(pr *plannedRoute) *route.Route {\n\tpath := pr.path\n\tfee := path.rt.TotalAmount - path.deliver\n\tr.pending[path.rt] = &attemptMeta{\n\t\tdeliver: path.deliver,\n\t\tedges: path.edges,\n\t\tamounts: path.amounts,\n\t\tloads: path.loads,\n\t\tfee: fee,\n\t}\n\treturn path.rt\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment complete\")\n\t}\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\n\tfor len(r.planned) > 0 {\n\t\tpr := r.planned[0]\n\t\tif pr.path.deliver > amt {\n\t\t\tr.planned = nil\n\t\t\tbreak\n\t\t}\n\t\tfee := pr.path.rt.TotalAmount - pr.path.deliver\n\t\tbudget := r.remainingBudget()\n\t\tif budget != lnwire.MaxMilliSatoshi && fee > budget {\n\t\t\tr.planned = nil\n\t\t\tbreak\n\t\t}\n\n\t\tr.planned = r.planned[1:]\n\t\treturn r.issuePlanned(pr), nil\n\t}\n\n\tusedParts := r.partsUsed\n\tif inFlightHtlcs > usedParts {\n\t\tusedParts = inFlightHtlcs\n\t}\n\tif usedParts >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\tslots := r.spec.MaxParts - usedParts\n\n\tplan, err := r.createPlan(amt, slots)\n\tif err == nil {\n\t\tr.planned = plan[1:]\n\t\treturn r.issuePlanned(plan[0]), nil\n\t}\n\n\tif slots > 1 {\n\t\tcandidates := []lnwire.MilliSatoshi{\n\t\t\tceilDiv(amt, int(slots)),\n\t\t\tamt / 2,\n\t\t\tamt / 3,\n\t\t\tamt / 4,\n\t\t}\n\t\tbudget := r.remainingBudget()\n\t\tfor _, shard := range candidates {\n\t\t\tif shard <= 0 || shard >= amt {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif r.retryCap > 0 && shard > r.retryCap {\n\t\t\t\tshard = r.retryCap\n\t\t\t}\n\t\t\tpath, findErr := r.findRoute(\n\t\t\t\tshard, r.activeLoads,\n\t\t\t\tcopyUsage(r.activeLoads), budget,\n\t\t\t)\n\t\t\tif findErr == nil {\n\t\t\t\treturn r.issuePlanned(\n\t\t\t\t\t&plannedRoute{path: path},\n\t\t\t\t), nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, err\n}\n\nfunc classifyFailure(f any) (liquidity, policy bool) {\n\tswitch f.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\treturn true, false\n\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\t\treturn false, true\n\n\tdefault:\n\t\treturn false, false\n\t}\n}\n\nfunc (r *candidateRouter) fallbackMeta(rt *route.Route) *attemptMeta {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tmeta := &attemptMeta{\n\t\tdeliver: rt.Hops[len(rt.Hops)-1].AmtToForward,\n\t\tfee: rt.TotalAmount -\n\t\t\trt.Hops[len(rt.Hops)-1].AmtToForward,\n\t}\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\te := r.edgeIndex[key]\n\t\tif e == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamtOver := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamtOver = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tmeta.edges = append(meta.edges, e)\n\t\tmeta.amounts = append(meta.amounts, amtOver)\n\t\tmeta.loads = append(\n\t\t\tmeta.loads, amtOver+r.activeLoads[key],\n\t\t)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\treturn meta\n}\n\nfunc failureEdgeIndex(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tr.attempts++\n\n\tmeta := r.pending[rt]\n\tdelete(r.pending, rt)\n\tif meta == nil {\n\t\tmeta = r.fallbackMeta(rt)\n\t}\n\tif meta == nil {\n\t\tr.planned = nil\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, e := range meta.edges {\n\t\t\trecordSuccess(e.key, meta.loads[i])\n\t\t\tr.activeLoads[e.key] += meta.amounts[i]\n\t\t\tif r.tempPenalty[e.key] > 0 {\n\t\t\t\tr.tempPenalty[e.key] *= 0.35\n\t\t\t}\n\t\t}\n\t\tr.committedFees += meta.fee\n\t\tr.partsUsed++\n\t\tr.retryCap = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tliquidity, policy := classifyFailure(result.Failure)\n\tfailIdx := failureEdgeIndex(rt, result.FailureSource)\n\tif failIdx < 0 && liquidity && len(meta.edges) == 1 {\n\t\tfailIdx = 0\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.edges) {\n\t\tfor i := 0; i < failIdx; i++ {\n\t\t\trecordSuccess(meta.edges[i].key, meta.loads[i])\n\t\t}\n\n\t\tfailed := meta.edges[failIdx]\n\t\tif liquidity {\n\t\t\trecordFailure(failed.key, meta.loads[failIdx])\n\t\t\tr.tempPenalty[failed.key] += 650000\n\n\t\t\tnextCap := meta.deliver * 45 / 100\n\t\t\tif nextCap < 1 {\n\t\t\t\tnextCap = 1\n\t\t\t}\n\t\t\tif r.retryCap == 0 || nextCap < r.retryCap {\n\t\t\t\tr.retryCap = nextCap\n\t\t\t}\n\t\t}\n\t\tif policy {\n\t\t\tr.policyBad[failed.key] = true\n\t\t}\n\t}\n\n\treturn nil\n}\n\nvar _ routing.SimRouter = (*candidateRouter)(nil)\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\"candidate-router(%v->%v)\",\n\t\tr.source, r.spec.Target)\n}"
}
},
{
"id": 2,
"parent": 0,
"score": 0.1255,
"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\tprobabilityPriceMsat = 420000.0\n\treservationPenalty = 650000.0\n\tpolicyPenalty = 2000000.0\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tokCount uint32\n\tbadCount uint32\n}\n\nvar candidateSharedBeliefs = struct {\n\tsync.RWMutex\n\tm map[candidateEdgeKey]candidateBelief\n}{\n\tm: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tfee := int64(e.baseFeeMsat) +\n\t\tint64(amt)*int64(e.feeRatePPM)/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) inboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tfee := int64(e.inboundBase) +\n\t\tint64(amt)*int64(e.inboundRate)/1_000_000\n\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) usable(shardAmt,\n\ttotalLoad lnwire.MilliSatoshi) bool {\n\n\tif shardAmt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && shardAmt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn totalLoad <= e.capacity\n}\n\ntype candidatePlanRoute struct {\n\trt *route.Route\n\tprobability float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tpolicyBad map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplan []candidatePlanRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tlastFailedAmt 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\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tpolicyBad: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\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\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\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(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tscore float64\n\tfee lnwire.MilliSatoshi\n\toutAmt lnwire.MilliSatoshi\n\toutEdge *candidateEdge\n\triskNats float64\n}\n\ntype candidateQueue []*candidateQueueItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateQueueItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidateClampProbability(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 candidatePrior(amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.04)\n\thighMode := 0.5 / (1 + math.Exp((x-0.90)*18))\n\n\treturn candidateClampProbability(lowMode + highMode)\n}\n\nfunc candidateReadBelief(key candidateEdgeKey) candidateBelief {\n\tcandidateSharedBeliefs.RLock()\n\tbelief := candidateSharedBeliefs.m[key]\n\tcandidateSharedBeliefs.RUnlock()\n\treturn belief\n}\n\nfunc candidateRecordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tbelief := candidateSharedBeliefs.m[key]\n\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tbelief.okCount++\n\tcandidateSharedBeliefs.m[key] = belief\n\tcandidateSharedBeliefs.Unlock()\n}\n\nfunc candidateRecordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tbelief := candidateSharedBeliefs.m[key]\n\n\t// A failure below a previously successful total load is likely\n\t// transient contention. Do not turn it into a contradictory bound.\n\tif amt > belief.lowerOK &&\n\t\t(belief.upperBad == 0 || amt < belief.upperBad) {\n\n\t\tbelief.upperBad = amt\n\t}\n\tbelief.badCount++\n\tcandidateSharedBeliefs.m[key] = belief\n\tcandidateSharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\tif !ok || load > balance {\n\t\t\treturn 0.005\n\t\t}\n\t\treturn 0.995\n\t}\n\n\tprior := candidatePrior(load, edge.capacity)\n\tbelief := candidateReadBelief(edge.key)\n\n\tif belief.lowerOK != 0 && load <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperBad != 0 && load >= belief.upperBad {\n\t\treturn 0.012\n\t}\n\n\tif belief.lowerOK != 0 && belief.upperBad > belief.lowerOK {\n\t\twidth := float64(belief.upperBad - belief.lowerOK)\n\t\tpos := float64(load-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tempirical := 0.995*(1-pos) + 0.012*pos\n\t\tconfidence := float64(belief.okCount+belief.badCount) / 5\n\t\tif confidence > 0.8 {\n\t\t\tconfidence = 0.8\n\t\t}\n\t\treturn candidateClampProbability(\n\t\t\tprior*(1-confidence) + empirical*confidence,\n\t\t)\n\t}\n\n\tif belief.upperBad != 0 {\n\t\tx := float64(load) / float64(belief.upperBad)\n\t\tif x > 0.5 {\n\t\t\tsoftener := 1 - 0.75*(x-0.5)/0.5\n\t\t\tif softener < 0.25 {\n\t\t\t\tsoftener = 0.25\n\t\t\t}\n\t\t\tprior *= softener\n\t\t}\n\t}\n\n\treturn candidateClampProbability(prior)\n}\n\nfunc candidateNodeFee(outEdge, incoming *candidateEdge,\n\toutAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif outEdge == nil {\n\t\treturn 0\n\t}\n\n\toutbound := outEdge.fee(outAmt)\n\tinbound := incoming.inboundFee(outAmt + outbound)\n\ttotal := int64(outbound) + int64(inbound)\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\nfunc candidateRiskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn probabilityPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 3\n\tif price < 15000 {\n\t\tprice = 15000\n\t}\n\tif price > probabilityPriceMsat {\n\t\tprice = probabilityPriceMsat\n\t}\n\treturn price\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*route.Route, float64, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, 0, errors.New(\"invalid route amount\")\n\t}\n\n\triskPrice := candidateRiskPrice(feeCap)\n\tdist := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = 0\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\toutAmt: amt,\n\t})\n\n\tvar sourceItem *candidateQueueItem\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateQueueItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.score > best+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceItem = item\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tnodeFee := candidateNodeFee(\n\t\t\t\titem.outEdge, edge, item.outAmt,\n\t\t\t)\n\t\t\tchannelAmt := item.outAmt + nodeFee\n\t\t\tload := channelAmt + reservations[edge.key]\n\n\t\t\tif !edge.usable(channelAmt, load) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tnewFee := item.fee + nodeFee\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\tnewFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, load)\n\t\t\trisk := -math.Log(probability)\n\t\t\tscore := item.score + float64(nodeFee) +\n\t\t\t\triskPrice*risk + 1500\n\n\t\t\tif reservations[edge.key] != 0 {\n\t\t\t\tscore += reservationPenalty *\n\t\t\t\t\t(1 + float64(reservations[edge.key])/\n\t\t\t\t\t\tfloat64(edge.capacity+1))\n\t\t\t}\n\t\t\tif count := r.policyBad[edge.key]; count != 0 {\n\t\t\t\tscore += policyPenalty * float64(count)\n\t\t\t}\n\n\t\t\told, exists := dist[edge.from]\n\t\t\tif exists && old <= score {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.from] = score\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &candidateQueueItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: score,\n\t\t\t\tfee: newFee,\n\t\t\t\toutAmt: channelAmt,\n\t\t\t\toutEdge: edge,\n\t\t\t\triskNats: item.riskNats + risk,\n\t\t\t})\n\t\t}\n\t}\n\n\tif sourceItem == nil {\n\t\treturn nil, 0, 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, 0, err\n\t}\n\n\tfee := rt.TotalAmount - amt\n\tif feeCap != lnwire.MaxMilliSatoshi && fee > feeCap {\n\t\treturn nil, 0, 0, errors.New(\"no route within fee budget\")\n\t}\n\n\tprobability := math.Exp(-sourceItem.riskNats)\n\tif probability < 1e-12 {\n\t\tprobability = 1e-12\n\t}\n\n\treturn rt, probability, sourceItem.score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(\n\tamt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\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\tfee := candidateNodeFee(\n\t\t\tpath[i+1], path[i], amounts[i+1],\n\t\t)\n\t\tamounts[i] = amounts[i+1] + fee\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforwardAmt := amt\n\t\toutgoingExpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforwardAmt = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forwardAmt,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteFee(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc candidateRouteDelivered(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateForEachRouteEdge(rt *route.Route,\n\tfn func(candidateEdgeKey, lnwire.MilliSatoshi)) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tfn(key, amt)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc candidateAddReservation(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tcandidateForEachRouteEdge(\n\t\trt, func(key candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\t\t\treservations[key] += amt\n\t\t},\n\t)\n}\n\nfunc (r *candidateRouter) remainingBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tdivisor int) lnwire.MilliSatoshi {\n\n\treturn (amt + lnwire.MilliSatoshi(divisor) - 1) /\n\t\tlnwire.MilliSatoshi(divisor)\n}\n\nfunc candidateUniqueAmounts(\n\tvalues []lnwire.MilliSatoshi,\n\tminimum, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tresult := make([]lnwire.MilliSatoshi, 0, len(values))\n\n\tfor _, value := range values {\n\t\tif value < minimum || value > maximum || seen[value] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[value] = true\n\t\tresult = append(result, value)\n\t}\n\n\treturn result\n}\n\nfunc (r *candidateRouter) buildPlan(\n\ttotal lnwire.MilliSatoshi, parts int,\n\tbudget lnwire.MilliSatoshi, sizeBias float64,\n) ([]candidatePlanRoute, bool) {\n\n\treservations := candidateCopyReservations(r.reserved)\n\tremaining := total\n\tfeeLeft := budget\n\tplan := make([]candidatePlanRoute, 0, parts)\n\n\tfor part := 0; part < parts; part++ {\n\t\tpartsLeft := parts - part\n\t\tif partsLeft == 1 {\n\t\t\trt, probability, _, err := r.findRoute(\n\t\t\t\tremaining, reservations, feeLeft,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tplan = append(plan, candidatePlanRoute{\n\t\t\t\trt: rt,\n\t\t\t\tprobability: probability,\n\t\t\t})\n\t\t\tremaining = 0\n\t\t\tbreak\n\t\t}\n\n\t\tequal := candidateCeilDiv(remaining, partsLeft)\n\t\tmaximum := remaining - lnwire.MilliSatoshi(partsLeft-1)\n\n\t\tvalues := []lnwire.MilliSatoshi{\n\t\t\tequal * 135 / 100,\n\t\t\tequal * 115 / 100,\n\t\t\tequal,\n\t\t\tequal * 85 / 100,\n\t\t\tequal * 70 / 100,\n\t\t}\n\t\tif r.lastFailedAmt > 0 {\n\t\t\tvalues = append(\n\t\t\t\tvalues, r.lastFailedAmt*65/100,\n\t\t\t)\n\t\t}\n\n\t\tamounts := candidateUniqueAmounts(\n\t\t\tvalues, 1, maximum,\n\t\t)\n\n\t\tvar chosen *candidatePlanRoute\n\t\tvar chosenMetric float64\n\n\t\tfor _, shardAmt := range amounts {\n\t\t\trt, probability, score, err := r.findRoute(\n\t\t\t\tshardAmt, reservations, feeLeft,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tratio := float64(shardAmt) / float64(equal)\n\t\t\tmetric := score/probabilityPriceMsat -\n\t\t\t\tsizeBias*math.Log(ratio)\n\n\t\t\tif chosen == nil || metric < chosenMetric {\n\t\t\t\tchosen = &candidatePlanRoute{\n\t\t\t\t\trt: rt,\n\t\t\t\t\tprobability: probability,\n\t\t\t\t}\n\t\t\t\tchosenMetric = metric\n\t\t\t}\n\t\t}\n\n\t\tif chosen == nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tplan = append(plan, *chosen)\n\t\tshardAmt := candidateRouteDelivered(chosen.rt)\n\t\tremaining -= shardAmt\n\n\t\tfee := candidateRouteFee(chosen.rt)\n\t\tif feeLeft != lnwire.MaxMilliSatoshi {\n\t\t\tif fee > feeLeft {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tfeeLeft -= fee\n\t\t}\n\n\t\tcandidateAddReservation(reservations, chosen.rt)\n\t}\n\n\treturn plan, remaining == 0\n}\n\nfunc candidatePlanUtility(\n\tplan []candidatePlanRoute,\n\tbudget lnwire.MilliSatoshi) float64 {\n\n\triskPrice := candidateRiskPrice(budget)\n\tutility := 0.18 * float64(len(plan)-1)\n\n\tfor _, planned := range plan {\n\t\tp := planned.probability\n\t\tif p < 1e-12 {\n\t\t\tp = 1e-12\n\t\t}\n\t\tutility += -math.Log(p)\n\t\tutility += float64(candidateRouteFee(planned.rt)) / riskPrice\n\t\tutility += 0.025 * float64(len(planned.rt.Hops))\n\t}\n\n\treturn utility\n}\n\nfunc (r *candidateRouter) makePlan(\n\tamt lnwire.MilliSatoshi, slots int,\n) []candidatePlanRoute {\n\n\tif slots < 1 {\n\t\treturn nil\n\t}\n\n\tbudget := r.remainingBudget()\n\tbestUtility := math.Inf(1)\n\tvar best []candidatePlanRoute\n\n\tbiases := []float64{0, 0.55, -0.35}\n\tfor parts := 1; parts <= slots; parts++ {\n\t\ttries := biases\n\t\tif parts == 1 {\n\t\t\ttries = biases[:1]\n\t\t}\n\n\t\tfor _, bias := range tries {\n\t\t\tplan, ok := r.buildPlan(\n\t\t\t\tamt, parts, budget, bias,\n\t\t\t)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tutility := candidatePlanUtility(plan, budget)\n\t\t\tif utility < bestUtility {\n\t\t\t\tbestUtility = utility\n\t\t\t\tbest = plan\n\t\t\t}\n\t\t}\n\t}\n\n\treturn best\n}\n\nfunc (r *candidateRouter) rebuildReservations() {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tfor _, rt := range r.held {\n\t\tcandidateAddReservation(r.reserved, rt)\n\t}\n}\n\nfunc (r *candidateRouter) syncHeld(inFlightHtlcs uint32) {\n\tif int(inFlightHtlcs) >= len(r.held) {\n\t\treturn\n\t}\n\n\tr.held = r.held[:int(inFlightHtlcs)]\n\tr.rebuildReservations()\n\tr.plan = nil\n}\n\nfunc (r *candidateRouter) fallbackRoute(\n\tamt lnwire.MilliSatoshi, slots int,\n) (*route.Route, error) {\n\n\tminimum := candidateCeilDiv(amt, slots)\n\tvalues := candidateUniqueAmounts(\n\t\t[]lnwire.MilliSatoshi{\n\t\t\tamt,\n\t\t\tamt * 3 / 4,\n\t\t\tamt * 2 / 3,\n\t\t\tamt / 2,\n\t\t\tminimum,\n\t\t},\n\t\tminimum, amt,\n\t)\n\n\tvar best *candidatePlanRoute\n\tbestMetric := math.Inf(1)\n\tbudget := r.remainingBudget()\n\n\tfor _, shardAmt := range values {\n\t\trt, probability, score, err := r.findRoute(\n\t\t\tshardAmt, r.reserved, budget,\n\t\t)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tmetric := score/probabilityPriceMsat -\n\t\t\t0.25*math.Log(float64(shardAmt)/float64(minimum))\n\t\tmetric += -math.Log(probability) * 0.15\n\n\t\tif metric < bestMetric {\n\t\t\tbestMetric = metric\n\t\t\tbest = &candidatePlanRoute{\n\t\t\t\trt: rt,\n\t\t\t\tprobability: probability,\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best.rt, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(\n\tamt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment complete\")\n\t}\n\n\tr.syncHeld(inFlightHtlcs)\n\n\tif r.spec.MaxParts <= inFlightHtlcs {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\tslots := int(r.spec.MaxParts - inFlightHtlcs)\n\n\tfor len(r.plan) != 0 {\n\t\tnext := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tdelivered := candidateRouteDelivered(next.rt)\n\t\tfee := candidateRouteFee(next.rt)\n\t\tbudget := r.remainingBudget()\n\n\t\tif delivered <= amt &&\n\t\t\t(budget == lnwire.MaxMilliSatoshi || fee <= budget) {\n\n\t\t\treturn next.rt, nil\n\t\t}\n\n\t\tr.plan = nil\n\t}\n\n\tr.plan = r.makePlan(amt, slots)\n\tif len(r.plan) != 0 {\n\t\tnext := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\t\treturn next.rt, nil\n\t}\n\n\treturn r.fallbackRoute(amt, slots)\n}\n\nfunc (r *candidateRouter) routeLoads(\n\trt *route.Route) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tloads := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tcandidateForEachRouteEdge(\n\t\trt, func(key candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\t\t\tloads[key] = amt + r.reserved[key]\n\t\t},\n\t)\n\treturn loads\n}\n\nfunc (r *candidateRouter) ReportAttempt(\n\tattemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tloads := r.routeLoads(rt)\n\n\tif result.Failure == nil {\n\t\tcandidateForEachRouteEdge(\n\t\t\trt, func(key candidateEdgeKey, _ lnwire.MilliSatoshi) {\n\t\t\t\tcandidateRecordSuccess(key, loads[key])\n\t\t\t},\n\t\t)\n\n\t\tr.committedFees += candidateRouteFee(rt)\n\t\tr.held = append(r.held, rt)\n\t\tcandidateAddReservation(r.reserved, rt)\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tedgeKeys := make([]candidateEdgeKey, 0, len(rt.Hops))\n\tedgeAmts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tcandidateForEachRouteEdge(\n\t\trt, func(key candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\t\t\tedgeKeys = append(edgeKeys, key)\n\t\t\tedgeAmts = append(edgeAmts, amt)\n\t\t},\n\t)\n\n\tif failIdx > len(edgeKeys) {\n\t\tfailIdx = len(edgeKeys)\n\t}\n\n\tif failIdx >= 0 {\n\t\tfor i := 0; i < failIdx && i < len(edgeKeys); i++ {\n\t\t\tcandidateRecordSuccess(\n\t\t\t\tedgeKeys[i], loads[edgeKeys[i]],\n\t\t\t)\n\t\t}\n\t}\n\n\tcode := result.Failure.Code()\n\tif failIdx >= 0 && failIdx < len(edgeKeys) {\n\t\tkey := edgeKeys[failIdx]\n\t\tload := loads[key]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tcandidateRecordFailure(key, load)\n\t\t\tr.lastFailedAmt = edgeAmts[failIdx]\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.policyBad[key]++\n\t\t}\n\t}\n\n\tr.plan = nil\n\treturn nil\n}"
}
},
{
"id": 3,
"parent": 0,
"score": 0.1881,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskPriceMsat = 420000.0\n\tpartCostMsat = 12000.0\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFee int64\n\tfeeRate int64\n\tdelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\t// This fee belongs to the receiving node and applies when an HTLC\n\t// arrives over this edge.\n\tinboundBase int64\n\tinboundRate int64\n}\n\nfunc (e *candidateEdge) outboundFee(amt lnwire.MilliSatoshi) int64 {\n\tfee := e.baseFee + int64(amt)*e.feeRate/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn fee\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\n// nodeFee returns the total fee charged by the node between in and out.\n// The inbound component is charged on the forwarded amount plus the\n// ordinary outbound fee, and the combined fee is floored at zero.\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif out == nil {\n\t\treturn 0\n\t}\n\n\toutFee := out.outboundFee(amt)\n\tinFee := in.inboundBase +\n\t\t(int64(amt)+outFee)*in.inboundRate/1_000_000\n\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc edgeProbability(key edgeKey, amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\n\tif !ok || b.conf == 0 {\n\t\treturn prior\n\t}\n\n\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\treturn 0.992\n\t}\n\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\treturn 0.012\n\t}\n\n\tevidence := prior\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(amt-b.lowerOK) / width\n\t\tevidence = 0.985 - 0.973*pos\n\n\tcase b.lowerOK > 0:\n\t\theadroom := capacity - b.lowerOK\n\t\tif headroom > 0 {\n\t\t\tpos := float64(amt-b.lowerOK) / float64(headroom)\n\t\t\tevidence = 0.985*(1-pos) + 0.012*pos\n\t\t}\n\n\tcase b.upperBad > 0:\n\t\tpos := float64(amt) / float64(b.upperBad)\n\t\tevidence = 0.985*(1-pos) + 0.012*pos\n\t}\n\n\tif evidence < 0.012 {\n\t\tevidence = 0.012\n\t}\n\tif evidence > 0.992 {\n\t\tevidence = 0.992\n\t}\n\n\tweight := b.conf / (b.conf + 2)\n\tif weight > 0.85 {\n\t\tweight = 0.85\n\t}\n\n\tp := prior*(1-weight) + evidence*weight\n\tif p < 0.012 {\n\t\treturn 0.012\n\t}\n\tif p > 0.992 {\n\t\treturn 0.992\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool) {\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\n\t\t// Contradictions can arise from drift or temporarily held\n\t\t// liquidity. Retain useful history without creating impossible\n\t\t// hard bounds.\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = amt * 3 / 4\n\t\t}\n\t}\n\n\tb.conf++\n\tif b.conf > 8 {\n\t\tb.conf = 8\n\t}\n\tsharedBeliefs.m[key] = b\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\n\tcommittedFees lnwire.MilliSatoshi\n\n\tplanned []*route.Route\n\n\toutstanding map[*route.Route]bool\n\theld []*route.Route\n\tattemptLoad map[*route.Route]map[edgeKey]lnwire.MilliSatoshi\n\n\tavoid map[edgeKey]float64\n\tfailedAt map[edgeKey]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\tincoming: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocal: localBalances,\n\t\toutstanding: make(map[*route.Route]bool),\n\t\tattemptLoad: make(map[*route.Route]map[edgeKey]lnwire.MilliSatoshi),\n\t\tavoid: make(map[edgeKey]float64),\n\t\tfailedAt: make(map[edgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\n\t\t\t\t\tbaseFee: int64(pol.FeeBaseMSat),\n\t\t\t\t\tfeeRate: int64(\n\t\t\t\t\t\tpol.FeeProportionalMillionths,\n\t\t\t\t\t),\n\t\t\t\t\tdelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\tr.edges[key] = edge\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *searchQueue) Push(x any) {\n\titem := x.(*searchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype foundRoute struct {\n\trt *route.Route\n\tscore float64\n}\n\nfunc pathContains(path []*candidateEdge, node route.Vertex) bool {\n\tfor _, edge := range path {\n\t\tif edge.from == node || edge.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *candidateRouter) reservationLedger() map[edgeKey]lnwire.MilliSatoshi {\n\tres := make(map[edgeKey]lnwire.MilliSatoshi)\n\n\tfor _, rt := range r.held {\n\t\tr.addRouteReservation(res, rt)\n\t}\n\tfor rt := range r.outstanding {\n\t\tr.addRouteReservation(res, rt)\n\t}\n\n\treturn res\n}\n\nfunc routeAmounts(rt *route.Route) map[edgeKey]lnwire.MilliSatoshi {\n\tamounts := make(map[edgeKey]lnwire.MilliSatoshi)\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t}\n\t\tamounts[key] += amt\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn amounts\n}\n\nfunc (r *candidateRouter) addRouteReservation(\n\tres map[edgeKey]lnwire.MilliSatoshi, rt *route.Route) {\n\n\tfor key, amt := range routeAmounts(rt) {\n\t\tres[key] += amt\n\t}\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treceived := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= received {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - received\n}\n\nfunc routeReceiverAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) feeLeft() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tused := r.committedFees\n\tfor rt := range r.outstanding {\n\t\tused += routeFee(rt)\n\t}\n\n\tif used >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - used\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver, maxFee lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi) (*foundRoute, 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 target\")\n\t}\n\n\tbest := make(map[route.Vertex]float64)\n\tbest[r.spec.Target] = 0\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t\tpath: nil,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tif known, ok := best[item.node]; ok && item.score > known {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(deliver, item.path)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif routeFee(rt) > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn &foundRoute{rt: rt, score: item.score}, nil\n\t\t}\n\n\t\tvar out *candidateEdge\n\t\tif len(item.path) > 0 {\n\t\t\tout = item.path[0]\n\t\t}\n\n\t\tfor _, in := range r.incoming[item.node] {\n\t\t\tif pathContains(item.path, in.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := nodeFee(in, out, item.amount)\n\t\t\tif int64(item.amount) > math.MaxInt64-int64(fee) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcrossing := item.amount + fee\n\n\t\t\tif !in.usableHTLC(crossing) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := crossing + reserved[in.key]\n\t\t\tif load > in.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif in.from == r.source {\n\t\t\t\tbalance, ok := r.local[in.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\taccruedFee := crossing - deliver\n\t\t\tif accruedFee > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := edgeProbability(in.key, load, in.capacity)\n\t\t\trisk := -math.Log(p)\n\n\t\t\tif penalty := r.avoid[in.key]; penalty > 0 {\n\t\t\t\tscale := 1.0\n\t\t\t\tif failed := r.failedAt[in.key]; failed > 0 &&\n\t\t\t\t\tload < failed {\n\n\t\t\t\t\tscale = 0.2 + 0.8*\n\t\t\t\t\t\tfloat64(load)/float64(failed)\n\t\t\t\t}\n\t\t\t\trisk += penalty * scale\n\t\t\t}\n\n\t\t\t// Reusing an already reserved corridor is legal, but it is\n\t\t\t// deliberately expensive so an MPP set fans out when the\n\t\t\t// graph offers alternatives.\n\t\t\treuseCost := 0.0\n\t\t\tif reserved[in.key] > 0 {\n\t\t\t\tratio := float64(reserved[in.key]) /\n\t\t\t\t\tfloat64(in.capacity)\n\t\t\t\treuseCost = 250000 + 350000*ratio\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) +\n\t\t\t\triskPriceMsat*risk + reuseCost\n\n\t\t\tif old, ok := best[in.from]; ok && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = in\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tbest[in.from] = score\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: in.from,\n\t\t\t\tamount: crossing,\n\t\t\t\tscore: score,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(\n\tdeliver lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tamounts[i] = amounts[i+1] + fee\n\t\texpiries[i] = expiries[i+1] + uint32(path[i+1].delta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\ntype routePlan struct {\n\troutes []*route.Route\n\tscore float64\n}\n\nfunc (r *candidateRouter) constructPlan(\n\ttotal lnwire.MilliSatoshi, parts int, skew bool,\n\tbaseReserved map[edgeKey]lnwire.MilliSatoshi,\n\tbudget lnwire.MilliSatoshi) (*routePlan, error) {\n\n\treserved := make(map[edgeKey]lnwire.MilliSatoshi, len(baseReserved))\n\tfor key, amt := range baseReserved {\n\t\treserved[key] = amt\n\t}\n\n\tleftAmt := total\n\tleftBudget := budget\n\tplan := &routePlan{}\n\n\tfor i := 0; i < parts; i++ {\n\t\tleftParts := parts - i\n\t\tshard := (leftAmt + lnwire.MilliSatoshi(leftParts) - 1) /\n\t\t\tlnwire.MilliSatoshi(leftParts)\n\n\t\t// A second family of candidate plans gives earlier, stronger\n\t\t// corridors a larger share. Equal plans remain available when\n\t\t// asymmetric allocation is not beneficial.\n\t\tif skew && leftParts > 1 {\n\t\t\tlarger := shard * 3 / 2\n\t\t\tminRemainder := lnwire.MilliSatoshi(leftParts - 1)\n\t\t\tif larger < leftAmt-minRemainder {\n\t\t\t\tshard = larger\n\t\t\t}\n\t\t}\n\n\t\tfound, err := r.findRoute(shard, leftBudget, reserved)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfee := routeFee(found.rt)\n\t\tif fee > leftBudget {\n\t\t\treturn nil, errors.New(\"route plan exceeds fee budget\")\n\t\t}\n\n\t\tplan.routes = append(plan.routes, found.rt)\n\t\tplan.score += found.score + partCostMsat\n\t\tr.addRouteReservation(reserved, found.rt)\n\n\t\tleftAmt -= shard\n\t\tif leftBudget != lnwire.MaxMilliSatoshi {\n\t\t\tleftBudget -= fee\n\t\t}\n\t}\n\n\tif leftAmt != 0 {\n\t\treturn nil, errors.New(\"incomplete route plan\")\n\t}\n\treturn plan, nil\n}\n\nfunc (r *candidateRouter) makePlan(\n\tamt lnwire.MilliSatoshi, slots uint32) error {\n\n\tif slots == 0 {\n\t\treturn errors.New(\"maximum parts already in flight\")\n\t}\n\n\tbudget := r.feeLeft()\n\tif budget == 0 && r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\treturn errors.New(\"fee budget exhausted\")\n\t}\n\n\treserved := r.reservationLedger()\n\tvar best *routePlan\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tequal, err := r.constructPlan(\n\t\t\tamt, parts, false, reserved, budget,\n\t\t)\n\t\tif err == nil && (best == nil || equal.score < best.score) {\n\t\t\tbest = equal\n\t\t}\n\n\t\tif parts > 1 {\n\t\t\tskewed, err := r.constructPlan(\n\t\t\t\tamt, parts, true, reserved, budget,\n\t\t\t)\n\t\t\tif err == nil &&\n\t\t\t\t(best == nil || skewed.score < best.score) {\n\n\t\t\t\tbest = skewed\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn errors.New(\"no route found\")\n\t}\n\n\tr.planned = best.routes\n\treturn nil\n}\n\nfunc (r *candidateRouter) reconcileHeld(inFlight uint32) {\n\toutstanding := len(r.outstanding)\n\twantedHeld := int(inFlight) - outstanding\n\tif wantedHeld < 0 {\n\t\twantedHeld = 0\n\t}\n\n\tif len(r.held) > wantedHeld {\n\t\tdrop := len(r.held) - wantedHeld\n\t\tr.held = append([]*route.Route(nil), r.held[drop:]...)\n\t}\n}\n\nfunc (r *candidateRouter) markOutstanding(rt *route.Route) {\n\tshardAmounts := routeAmounts(rt)\n\treserved := r.reservationLedger()\n\tloads := make(map[edgeKey]lnwire.MilliSatoshi, len(shardAmounts))\n\n\tfor key, shard := range shardAmounts {\n\t\tloads[key] = reserved[key] + shard\n\n\t\t// If attempts are launched before earlier attempts resolve,\n\t\t// their observations concern their aggregate concurrent load.\n\t\tfor other := range r.outstanding {\n\t\t\totherAmounts := routeAmounts(other)\n\t\t\tif _, shared := otherAmounts[key]; !shared {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif prior := r.attemptLoad[other]; prior != nil {\n\t\t\t\tprior[key] += shard\n\t\t\t}\n\t\t}\n\t}\n\n\tr.attemptLoad[rt] = loads\n\tr.outstanding[rt] = true\n}\n\nfunc (r *candidateRouter) RequestRoute(\n\tamt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tr.reconcileHeld(inFlightHtlcs)\n\n\tfor len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tr.planned = r.planned[1:]\n\n\t\tif routeReceiverAmount(rt) > amt || routeFee(rt) > r.feeLeft() {\n\t\t\tr.planned = nil\n\t\t\tbreak\n\t\t}\n\n\t\tr.markOutstanding(rt)\n\t\treturn rt, nil\n\t}\n\n\tslots := uint32(0)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tslots = r.spec.MaxParts - inFlightHtlcs\n\t}\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tif err := r.makePlan(amt, slots); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(r.planned) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt := r.planned[0]\n\tr.planned = r.planned[1:]\n\tr.markOutstanding(rt)\n\treturn rt, nil\n}\n\nfunc failureIndex(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc routeEdgeKeys(rt *route.Route) []edgeKey {\n\tkeys := make([]edgeKey, 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}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys\n}\n\nfunc (r *candidateRouter) ReportAttempt(\n\tattemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tloads := r.attemptLoad[rt]\n\tif loads == nil {\n\t\tloads = make(map[edgeKey]lnwire.MilliSatoshi)\n\t\treserved := r.reservationLedger()\n\t\tfor key, shard := range routeAmounts(rt) {\n\t\t\tloads[key] = reserved[key] + shard\n\t\t}\n\t}\n\n\tdelete(r.attemptLoad, rt)\n\tdelete(r.outstanding, rt)\n\n\tkeys := routeEdgeKeys(rt)\n\n\tif result.Failure == nil {\n\t\tfor _, key := range keys {\n\t\t\tupdateBelief(key, loads[key], true)\n\t\t\tr.avoid[key] *= 0.25\n\t\t}\n\n\t\tr.committedFees += routeFee(rt)\n\t\tr.held = append(r.held, rt)\n\t\treturn nil\n\t}\n\n\t// Any failure invalidates the unsent remainder of the route-set plan.\n\t// Already dispatched siblings remain accounted for separately.\n\tr.planned = nil\n\n\tfailIdx := failureIndex(rt, result.FailureSource)\n\tif failIdx < 0 {\n\t\treturn nil\n\t}\n\n\t// Every channel before the failing node carried the aggregate load.\n\tfor i := 0; i < failIdx && i < len(keys); i++ {\n\t\tkey := keys[i]\n\t\tupdateBelief(key, loads[key], true)\n\t\tr.avoid[key] *= 0.5\n\t}\n\n\tif failIdx >= len(keys) {\n\t\treturn nil\n\t}\n\n\tfailedKey := keys[failIdx]\n\tcode := result.Failure.Code()\n\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tupdateBelief(failedKey, loads[failedKey], false)\n\t\tr.failedAt[failedKey] = loads[failedKey]\n\t\tr.avoid[failedKey] += 1.2\n\t\tif r.avoid[failedKey] > 7 {\n\t\t\tr.avoid[failedKey] = 7\n\t\t}\n\t} else {\n\t\t// Fee and CLTV failures indicate that this exact advertised\n\t\t// corridor should not immediately be selected again. They do not\n\t\t// establish a liquidity upper bound.\n\t\tr.avoid[failedKey] += 5\n\t\tif r.avoid[failedKey] > 12 {\n\t\t\tr.avoid[failedKey] = 12\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\"bimodal-budget-router(%v)\", r.source)\n}"
}
},
{
"id": 4,
"parent": 0,
"score": 0.1231,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskPriceMsat = 420000.0\n\thopCostMsat = 2500.0\n\treuseCostMsat = 350000.0\n\tmaxPathHops = 30\n\tmaxLabels = 4\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\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\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tfee := e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn fee\n}\n\nfunc (e *candidateEdge) inboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\treturn lnwire.MilliSatoshi(\n\t\tint64(e.inboundBase) +\n\t\t\tint64(amt)*int64(e.inboundRate)/1_000_000,\n\t)\n}\n\nfunc (e *candidateEdge) shardUsable(\n\tamt lnwire.MilliSatoshi) bool {\n\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\n// nodeFee is the fee charged by the node reached over incoming. It combines\n// that node's outgoing policy with the inbound fee attached to incoming.\nfunc nodeFee(outgoing, incoming *candidateEdge,\n\tamtToForward lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := outgoing.fee(amtToForward)\n\tinFee := incoming.inboundFee(amtToForward + outFee)\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\treturn 0\n\t}\n\treturn total\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n}\n\ntype knowledgeKey struct {\n\tsource route.Vertex\n\tchanID uint64\n\tfrom route.Vertex\n\tcapacity lnwire.MilliSatoshi\n}\n\nvar sharedKnowledge = struct {\n\tsync.Mutex\n\tbeliefs map[knowledgeKey]liquidityBelief\n}{\n\tbeliefs: make(map[knowledgeKey]liquidityBelief),\n}\n\ntype heldShard struct {\n\trt *route.Route\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\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tuseCount map[edgeKey]int\n\theld []heldShard\n\n\tbadPolicy map[edgeKey]bool\n\tpenalty map[edgeKey]float64\n\n\tcommittedFees lnwire.MilliSatoshi\n\n\tplan []*route.Route\n\tplannedAmt 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\tincoming: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tuseCount: make(map[edgeKey]int),\n\t\tbadPolicy: make(map[edgeKey]bool),\n\t\tpenalty: make(map[edgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\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\te := &candidateEdge{\n\t\t\t\t\tkey: edgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t},\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[e.to] = append(r.incoming[e.to], e)\n\t\t\t\tr.edges[e.key] = e\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedKnowledge.Lock()\n\tfor key, edge := range r.edges {\n\t\tk := r.knowledgeKey(edge)\n\t\tif belief, ok := sharedKnowledge.beliefs[k]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedKnowledge.Unlock()\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) knowledgeKey(e *candidateEdge) knowledgeKey {\n\treturn knowledgeKey{\n\t\tsource: r.source,\n\t\tchanID: e.key.chanID,\n\t\tfrom: e.key.from,\n\t\tcapacity: e.capacity,\n\t}\n}\n\nfunc priorProbability(amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.035)\n\thighMode := 0.5 / (1 + math.Exp((x-0.88)/0.055))\n\tp := lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif e.from == r.source {\n\t\treturn 0.997\n\t}\n\n\tprior := priorProbability(load, e.capacity)\n\tb, ok := r.beliefs[e.key]\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif b.lowerOK > 0 && load <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\n\testimate := b.estimate\n\tif estimate <= 0 {\n\t\testimate = e.capacity / 2\n\t}\n\n\tscale := float64(e.capacity) * 0.055\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tz := (float64(load) - float64(estimate)) / scale\n\tif z > 30 {\n\t\tz = 30\n\t} else if z < -30 {\n\t\tz = -30\n\t}\n\tmodel := 1 / (1 + math.Exp(z))\n\n\tweight := b.conf / (b.conf + 2)\n\tp := prior*(1-weight) + model*weight\n\n\tif b.upperFail > 0 {\n\t\tif load >= b.upperFail {\n\t\t\tif p > 0.04 {\n\t\t\t\tp = 0.04\n\t\t\t}\n\t\t\tif p < 0.012 {\n\t\t\t\tp = 0.012\n\t\t\t}\n\t\t} else if load > b.lowerOK &&\n\t\t\tb.upperFail > b.lowerOK {\n\n\t\t\tspan := float64(b.upperFail - b.lowerOK)\n\t\t\tleft := float64(b.upperFail-load) / span\n\t\t\tp *= 0.25 + 0.75*left\n\t\t}\n\t}\n\n\tif p < 0.012 {\n\t\treturn 0.012\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(b liquidityBelief, capacity,\n\tload lnwire.MilliSatoshi, success bool) liquidityBelief {\n\n\tif b.estimate <= 0 {\n\t\tb.estimate = capacity / 2\n\t}\n\n\tif success {\n\t\tif load > b.lowerOK {\n\t\t\tb.lowerOK = load\n\t\t}\n\t\tif b.upperFail > 0 && load >= b.upperFail {\n\t\t\tb.upperFail = 0\n\t\t}\n\n\t\ttarget := load + (capacity-load)*4/5\n\t\tif target > b.estimate {\n\t\t\tb.estimate = target\n\t\t}\n\t\tb.conf += 1.4\n\t} else {\n\t\tif b.lowerOK > 0 && load <= b.lowerOK {\n\t\t\tb.lowerOK = load * 3 / 4\n\t\t}\n\t\tif b.upperFail == 0 || load < b.upperFail {\n\t\t\tb.upperFail = load\n\t\t}\n\n\t\ttarget := load / 8\n\t\tif target < b.estimate {\n\t\t\tb.estimate = (b.estimate + target) / 2\n\t\t}\n\t\tb.conf += 1\n\t}\n\n\tif b.conf > 12 {\n\t\tb.conf = 12\n\t}\n\tif b.estimate < 0 {\n\t\tb.estimate = 0\n\t}\n\tif b.estimate > capacity {\n\t\tb.estimate = capacity\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) observe(e *candidateEdge,\n\tload lnwire.MilliSatoshi, success bool) {\n\n\tlocal := r.beliefs[e.key]\n\tlocal = updateBelief(local, e.capacity, load, success)\n\tr.beliefs[e.key] = local\n\n\tk := r.knowledgeKey(e)\n\tsharedKnowledge.Lock()\n\tglobal := sharedKnowledge.beliefs[k]\n\tglobal = updateBelief(global, e.capacity, load, success)\n\tsharedKnowledge.beliefs[k] = global\n\tsharedKnowledge.Unlock()\n}\n\ntype pathLabel struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tpath []*candidateEdge\n\tactive bool\n\tindex int\n}\n\ntype labelQueue []*pathLabel\n\nfunc (q labelQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q labelQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q labelQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *labelQueue) Push(x any) {\n\titem := x.(*pathLabel)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *labelQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc pathContains(path []*candidateEdge, vertex route.Vertex) bool {\n\tfor _, edge := range path {\n\t\tif edge.from == vertex || edge.to == vertex {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc insertLabel(labels map[route.Vertex][]*pathLabel,\n\tlabel *pathLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfiltered := current[:0]\n\n\tfor _, old := range current {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif old.score <= label.score &&\n\t\t\told.amount <= label.amount {\n\n\t\t\treturn false\n\t\t}\n\t\tif label.score <= old.score &&\n\t\t\tlabel.amount <= old.amount {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, old)\n\t}\n\n\tfiltered = append(filtered, label)\n\tif len(filtered) > maxLabels {\n\t\tminAmount := 0\n\t\tfor i := 1; i < len(filtered); i++ {\n\t\t\tif filtered[i].amount < filtered[minAmount].amount {\n\t\t\t\tminAmount = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range filtered {\n\t\t\tif i == minAmount {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 ||\n\t\t\t\tfiltered[i].score > filtered[worst].score {\n\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\t\tif worst < 0 {\n\t\t\tworst = len(filtered) - 1\n\t\t}\n\n\t\tevicted := filtered[worst]\n\t\tevicted.active = false\n\t\tfiltered = append(\n\t\t\tfiltered[:worst], filtered[worst+1:]...,\n\t\t)\n\t\tif evicted == label {\n\t\t\tlabels[label.node] = filtered\n\t\t\treturn false\n\t\t}\n\t}\n\n\tlabels[label.node] = filtered\n\tlabel.active = true\n\treturn true\n}\n\nfunc copyReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc copyUses(src map[edgeKey]int) map[edgeKey]int {\n\tdst := make(map[edgeKey]int, len(src))\n\tfor key, count := range src {\n\t\tdst[key] = count\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\tuses map[edgeKey]int,\n\tfeeCap lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid shard amount\")\n\t}\n\n\tprice := riskPriceMsat\n\tif feeCap != lnwire.MaxMilliSatoshi {\n\t\tbudgetPrice := float64(feeCap) * 2\n\t\tif budgetPrice < price {\n\t\t\tprice = budgetPrice\n\t\t}\n\t\tif price < 30000 {\n\t\t\tprice = 30000\n\t\t}\n\t}\n\n\tstart := &pathLabel{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tscore: 0,\n\t\tactive: true,\n\t}\n\n\tlabels := map[route.Vertex][]*pathLabel{\n\t\tr.spec.Target: {start},\n\t}\n\tpq := &labelQueue{}\n\theap.Push(pq, start)\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\trt, err := r.buildRoute(amt, current.path)\n\t\t\treturn rt, current.score, err\n\t\t}\n\t\tif current.hops >= maxPathHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, incoming := range r.incoming[current.node] {\n\t\t\tif r.badPolicy[incoming.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif pathContains(current.path, incoming.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := amt\n\t\t\taddedFee := lnwire.MilliSatoshi(0)\n\t\t\tif len(current.path) != 0 {\n\t\t\t\toutgoing := current.path[0]\n\t\t\t\taddedFee = nodeFee(\n\t\t\t\t\toutgoing, incoming, current.amount,\n\t\t\t\t)\n\t\t\t\tover = current.amount + addedFee\n\t\t\t}\n\n\t\t\tif !incoming.shardUsable(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := over + reserved[incoming.key]\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif incoming.from == r.source {\n\t\t\t\tbalance := r.localBalances[incoming.key.chanID]\n\t\t\t\tif load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := over - amt\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(incoming, load)\n\t\t\taddedScore := float64(addedFee) +\n\t\t\t\tprice*(-math.Log(probability)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tfloat64(uses[incoming.key])*reuseCostMsat +\n\t\t\t\tr.penalty[incoming.key]\n\n\t\t\tpath := make([]*candidateEdge, len(current.path)+1)\n\t\t\tpath[0] = incoming\n\t\t\tcopy(path[1:], current.path)\n\n\t\t\tnext := &pathLabel{\n\t\t\t\tnode: incoming.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: current.score + addedScore,\n\t\t\t\thops: current.hops + 1,\n\t\t\t\tpath: path,\n\t\t\t}\n\t\t\tif insertLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tnodeFee(path[i+1], path[i], amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.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 routeDelivery(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 routeAmount(rt *route.Route, index int) lnwire.MilliSatoshi {\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc routeEdge(rt *route.Route, index int) edgeKey {\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\treturn edgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc addRouteReservations(rt *route.Route,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\tuses map[edgeKey]int) {\n\n\tfor i := range rt.Hops {\n\t\tkey := routeEdge(rt, i)\n\t\treserved[key] += routeAmount(rt, i)\n\t\tuses[key]++\n\t}\n}\n\nfunc (r *candidateRouter) rebuildReservations() {\n\tr.reserved = make(map[edgeKey]lnwire.MilliSatoshi)\n\tr.useCount = make(map[edgeKey]int)\n\tfor _, held := range r.held {\n\t\taddRouteReservations(\n\t\t\theld.rt, r.reserved, r.useCount,\n\t\t)\n\t}\n}\n\nfunc (r *candidateRouter) reconcileHeld(inFlight uint32) bool {\n\tif int(inFlight) >= len(r.held) {\n\t\treturn false\n\t}\n\n\tr.held = r.held[:int(inFlight)]\n\tr.rebuildReservations()\n\treturn true\n}\n\nfunc weightedShard(remaining lnwire.MilliSatoshi,\n\tparts int, bias float64) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn remaining\n\t}\n\n\ttotalWeight := 0.0\n\tfor i := 0; i < parts; i++ {\n\t\ttotalWeight += math.Pow(bias, float64(parts-1-i))\n\t}\n\tfirstWeight := math.Pow(bias, float64(parts-1))\n\tshard := lnwire.MilliSatoshi(\n\t\tfloat64(remaining) * firstWeight / totalWeight,\n\t)\n\n\tif shard < 1 {\n\t\tshard = 1\n\t}\n\tmaximum := remaining - lnwire.MilliSatoshi(parts-1)\n\tif shard > maximum {\n\t\tshard = maximum\n\t}\n\treturn shard\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts int, bias float64,\n\tfeeBudget lnwire.MilliSatoshi) ([]*route.Route, float64, bool) {\n\n\treserved := copyReservations(r.reserved)\n\tuses := copyUses(r.useCount)\n\tremaining := total\n\tfeeRemaining := feeBudget\n\n\tplan := make([]*route.Route, 0, parts)\n\tscore := float64(parts-1) * 20000\n\n\tfor i := 0; i < parts; i++ {\n\t\tleft := parts - i\n\t\tshard := weightedShard(remaining, left, bias)\n\n\t\trt, routeScore, err := r.findRoute(\n\t\t\tshard, reserved, uses, feeRemaining,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tfee := rt.TotalAmount - shard\n\t\tif feeRemaining != lnwire.MaxMilliSatoshi {\n\t\t\tif fee > feeRemaining {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tfeeRemaining -= fee\n\t\t}\n\n\t\tplan = append(plan, rt)\n\t\tscore += routeScore\n\t\taddRouteReservations(rt, reserved, uses)\n\t\tremaining -= shard\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, 0, false\n\t}\n\treturn plan, score, true\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tif maxParts < 1 {\n\t\treturn nil\n\t}\n\n\tcounts := make([]int, 0, 11)\n\tadd := func(value int) {\n\t\tif value < 1 || value > maxParts {\n\t\t\treturn\n\t\t}\n\t\tfor _, existing := range counts {\n\t\t\tif existing == value {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tcounts = append(counts, value)\n\t}\n\n\tlimit := maxParts\n\tif limit > 8 {\n\t\tlimit = 8\n\t}\n\tfor i := 1; i <= limit; i++ {\n\t\tadd(i)\n\t}\n\tadd(10)\n\tadd(12)\n\tadd(maxParts)\n\n\treturn counts\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() (\n\tlnwire.MilliSatoshi, error) {\n\n\tlimit := r.spec.FeeLimitMsat\n\tif limit == lnwire.MaxMilliSatoshi {\n\t\treturn limit, nil\n\t}\n\tif r.committedFees > limit {\n\t\treturn 0, errors.New(\"fee budget exhausted\")\n\t}\n\treturn limit - r.committedFees, nil\n}\n\nfunc (r *candidateRouter) createPlan(amt lnwire.MilliSatoshi,\n\tinFlight uint32) error {\n\n\tmaxParts := int(r.spec.MaxParts)\n\tif maxParts < 1 {\n\t\tmaxParts = 1\n\t}\n\tavailable := maxParts - int(inFlight)\n\tif available < 1 {\n\t\treturn errors.New(\"maximum parts in flight\")\n\t}\n\n\tfeeBudget, err := r.remainingFeeBudget()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar best []*route.Route\n\tbestScore := math.Inf(1)\n\tbiases := []float64{1, 1.35, 0.74}\n\n\tfor _, parts := range candidatePartCounts(available) {\n\t\tfor _, bias := range biases {\n\t\t\tplan, score, ok := r.makePlan(\n\t\t\t\tamt, parts, bias, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\treturn errors.New(\"no route found\")\n\t}\n\n\tr.plan = best\n\tr.plannedAmt = amt\n\treturn 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 complete\")\n\t}\n\n\tif r.reconcileHeld(inFlightHtlcs) {\n\t\tr.plan = nil\n\t\tr.plannedAmt = 0\n\t}\n\n\tif len(r.plan) == 0 || r.plannedAmt != amt {\n\t\tr.plan = nil\n\t\tr.plannedAmt = 0\n\t\tif err := r.createPlan(amt, inFlightHtlcs); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\trt := r.plan[0]\n\tr.plan = r.plan[1:]\n\tdeliver := routeDelivery(rt)\n\tif deliver <= 0 || deliver > r.plannedAmt {\n\t\tr.plan = nil\n\t\tr.plannedAmt = 0\n\t\treturn nil, errors.New(\"invalid planned shard\")\n\t}\n\tr.plannedAmt -= deliver\n\n\treturn rt, nil\n}\n\nfunc 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(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdge(rt, i)\n\t\t\tedge := r.edges[key]\n\t\t\tif edge == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tload := routeAmount(rt, i) + r.reserved[key]\n\t\t\tr.observe(edge, load, true)\n\t\t}\n\n\t\tdeliver := routeDelivery(rt)\n\t\tif rt.TotalAmount >= deliver {\n\t\t\tr.committedFees += rt.TotalAmount - deliver\n\t\t}\n\t\tr.held = append(r.held, heldShard{rt: rt})\n\t\tr.rebuildReservations()\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.plannedAmt = 0\n\n\tfailIdx := failureIndex(rt, result.FailureSource)\n\tif failIdx >= 0 {\n\t\tfor i := 0; i < failIdx && i < len(rt.Hops); i++ {\n\t\t\tkey := routeEdge(rt, i)\n\t\t\tedge := r.edges[key]\n\t\t\tif edge == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tload := routeAmount(rt, i) + r.reserved[key]\n\t\t\tr.observe(edge, load, true)\n\t\t}\n\t}\n\n\tcode := result.Failure.Code()\n\tif failIdx >= 0 && failIdx < len(rt.Hops) {\n\t\tkey := routeEdge(rt, failIdx)\n\t\tedge := r.edges[key]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tif edge != nil {\n\t\t\t\tload := routeAmount(rt, failIdx) +\n\t\t\t\t\tr.reserved[key]\n\t\t\t\tr.observe(edge, load, false)\n\t\t\t}\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.badPolicy[key] = true\n\n\t\tdefault:\n\t\t\tr.penalty[key] += 175000\n\t\t}\n\t} else {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdge(rt, i)\n\t\t\tr.penalty[key] += 25000\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 5,
"parent": 0,
"score": 0.0809,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskPriceMsat = 420000.0\n\thopCostMsat = 8000.0\n\tminShardMsat = lnwire.MilliSatoshi(1000)\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n\tcapacity lnwire.MilliSatoshi\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tobserved uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\n}\n\nfunc (e *candidateEdge) outboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tfee := int64(e.baseFeeMsat) +\n\t\tint64(amt)*int64(e.feeRatePPM)/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) inboundFee(amt lnwire.MilliSatoshi) int64 {\n\treturn e.inboundBase + int64(amt)*e.inboundRate/1_000_000\n}\n\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := outgoing.outboundFee(amt)\n\tinBase := amt + outFee\n\tcombined := int64(outFee) + incoming.inboundFee(inBase)\n\tif combined < 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(combined)\n}\n\nfunc priorProbability(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.035)\n\thighMode := 0.49 / (1 + math.Exp((x-0.82)/0.08))\n\tp := lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\n\treturn p\n}\n\nfunc beliefProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := priorProbability(load, e.capacity)\n\n\tsharedBeliefs.Lock()\n\tbelief, ok := sharedBeliefs.values[e.key]\n\tsharedBeliefs.Unlock()\n\n\tif !ok || belief.observed == 0 {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn 0.993\n\t}\n\tif belief.upperBad > 0 && load >= belief.upperBad {\n\t\treturn 0.012\n\t}\n\n\tvar estimate lnwire.MilliSatoshi\n\tswitch {\n\tcase belief.lowerOK > 0 && belief.upperBad > belief.lowerOK:\n\t\testimate = belief.lowerOK +\n\t\t\t(belief.upperBad-belief.lowerOK)*35/100\n\n\tcase belief.upperBad > 0:\n\t\testimate = belief.upperBad * 30 / 100\n\n\tcase belief.lowerOK > 0:\n\t\testimate = belief.lowerOK * 5 / 4\n\t\tfallback := e.capacity * 3 / 4\n\t\tif estimate < fallback {\n\t\t\testimate = fallback\n\t\t}\n\t\tif estimate > e.capacity {\n\t\t\testimate = e.capacity\n\t\t}\n\n\tdefault:\n\t\treturn prior\n\t}\n\n\tscale := float64(e.capacity) * 0.06\n\tif scale < 1000 {\n\t\tscale = 1000\n\t}\n\tevidence := 1 / (1 + math.Exp(\n\t\t(float64(load)-float64(estimate))/scale,\n\t))\n\n\tconfidence := 1 - math.Exp(-float64(belief.observed)/2.5)\n\tif confidence > 0.90 {\n\t\tconfidence = 0.90\n\t}\n\n\tp := prior*(1-confidence) + evidence*confidence\n\tif p < 0.012 {\n\t\treturn 0.012\n\t}\n\tif p > 0.993 {\n\t\treturn 0.993\n\t}\n\n\treturn p\n}\n\nfunc recordSuccess(key edgeKey, amt lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tbelief := sharedBeliefs.values[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperBad > 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif belief.observed < 1000 {\n\t\tbelief.observed++\n\t}\n\tsharedBeliefs.values[key] = belief\n}\n\nfunc recordFailure(key edgeKey, amt lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tbelief := sharedBeliefs.values[key]\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.observed < 1000 {\n\t\tbelief.observed++\n\t}\n\tsharedBeliefs.values[key] = belief\n}\n\ntype recentFailure struct {\n\tamount lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype routeReservation struct {\n\tedges []edgeKey\n\tamount []lnwire.MilliSatoshi\n}\n\ntype attemptRecord struct {\n\tedges []edgeKey\n\tloads []lnwire.MilliSatoshi\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\tedgeByKey map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\trecent map[edgeKey]recentFailure\n\textra map[edgeKey]float64\n\n\theld []routeReservation\n\tplan []*route.Route\n\tattempts map[*route.Route]attemptRecord\n\n\tspentFee lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\trecent: make(map[edgeKey]recentFailure),\n\t\textra: make(map[edgeKey]float64),\n\t\tattempts: make(map[*route.Route]attemptRecord),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tcapacity := lnwire.NewMSatFromSatoshis(\n\t\t\t\t\tch.Capacity,\n\t\t\t\t)\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\tcapacity: capacity,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeByKey[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *searchQueue) Push(value any) {\n\titem := value.(*searchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\told[last] = nil\n\t*q = old[:last]\n\treturn item\n}\n\nfunc copyReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) currentReservations() map[edgeKey]lnwire.MilliSatoshi {\n\treserved := make(map[edgeKey]lnwire.MilliSatoshi)\n\tfor _, hold := range r.held {\n\t\tfor i, key := range hold.edges {\n\t\t\treserved[key] += hold.amount[i]\n\t\t}\n\t}\n\n\treturn reserved\n}\n\nfunc (r *candidateRouter) canUse(edge *candidateEdge,\n\tamount, load lnwire.MilliSatoshi) bool {\n\n\tif amount < edge.minHTLC {\n\t\treturn false\n\t}\n\tif edge.maxHTLC != 0 && amount > edge.maxHTLC {\n\t\treturn false\n\t}\n\tif amount > edge.capacity || load > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.from == r.source {\n\t\tbalance := r.localBalances[edge.chanID]\n\t\tif load > balance {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\treturn 0.998\n\t}\n\n\tp := beliefProbability(edge, load)\n\tif failure, ok := r.recent[edge.key]; ok && failure.amount > 0 {\n\t\tratio := float64(load) / float64(failure.amount)\n\t\tif ratio > 1 {\n\t\t\tratio = 1\n\t\t}\n\t\tif ratio < 0 {\n\t\t\tratio = 0\n\t\t}\n\n\t\tpenalty := math.Exp(\n\t\t\t-0.85 * float64(failure.count) * ratio,\n\t\t)\n\t\tp *= penalty\n\t}\n\n\tif p < 0.006 {\n\t\treturn 0.006\n\t}\n\tif p > 0.998 {\n\t\treturn 0.998\n\t}\n\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeScore(edge *candidateEdge,\n\tload, reserved lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(edge, load)\n\tscore := -math.Log(p)*riskPriceMsat + hopCostMsat\n\tscore += r.extra[edge.key]\n\n\tif reserved > 0 && edge.capacity > 0 {\n\t\tfraction := float64(reserved) / float64(edge.capacity)\n\t\tscore += 100000 + 700000*fraction\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) searchRoute(deliver lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\tfeeWeight float64) (*route.Route, 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 target\")\n\t}\n\n\tbest := make(map[route.Vertex]float64)\n\tbest[r.spec.Target] = 0\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tbestScore, ok := best[item.node]\n\t\tif !ok || item.score > bestScore+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\treturn r.buildRoute(item.path, deliver)\n\t\t}\n\n\t\tfor _, incoming := range r.incomingEdges[item.node] {\n\t\t\tedgeAmount := deliver\n\t\t\tfeeDelta := lnwire.MilliSatoshi(0)\n\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tif len(item.path) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\toutgoing := item.path[0]\n\t\t\t\tfeeDelta = forwardingFee(\n\t\t\t\t\tincoming, outgoing, item.amount,\n\t\t\t\t)\n\t\t\t\tedgeAmount = item.amount + feeDelta\n\t\t\t}\n\n\t\t\talready := reserved[incoming.key]\n\t\t\tload := edgeAmount + already\n\t\t\tif !r.canUse(incoming, edgeAmount, load) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := item.score +\n\t\t\t\tfloat64(feeDelta)*feeWeight +\n\t\t\t\tr.edgeScore(incoming, load, already)\n\n\t\t\told, exists := best[incoming.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = incoming\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tbest[incoming.from] = score\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: incoming.from,\n\t\t\t\tamount: edgeAmount,\n\t\t\t\tscore: score,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tdeliver lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := forwardingFee(path[i], path[i+1], amounts[i+1])\n\t\tamounts[i] = amounts[i+1] + fee\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\tfor i, edge := range path {\n\t\tif amounts[i] < edge.minHTLC {\n\t\t\treturn nil, errors.New(\"route below htlc minimum\")\n\t\t}\n\t\tif edge.maxHTLC != 0 && amounts[i] > edge.maxHTLC {\n\t\t\treturn nil, errors.New(\"route above htlc maximum\")\n\t\t}\n\t\tif amounts[i] > edge.capacity {\n\t\t\treturn nil, errors.New(\"route above channel capacity\")\n\t\t}\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc routeDelivered(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tdeliver := routeDelivered(rt)\n\tif rt == nil || rt.TotalAmount < deliver {\n\t\treturn 0\n\t}\n\n\treturn rt.TotalAmount - deliver\n}\n\nfunc (r *candidateRouter) routeReservation(\n\trt *route.Route) (routeReservation, bool) {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn routeReservation{}, false\n\t}\n\n\tresult := routeReservation{\n\t\tedges: make([]edgeKey, 0, len(rt.Hops)),\n\t\tamount: make([]lnwire.MilliSatoshi, 0, len(rt.Hops)),\n\t}\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tvar edge *candidateEdge\n\t\tfor key, candidate := range r.edgeByKey {\n\t\t\tif key.chanID == hop.ChannelID &&\n\t\t\t\tkey.from == from && key.to == hop.PubKeyBytes {\n\n\t\t\t\tedge = candidate\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif edge == nil {\n\t\t\treturn routeReservation{}, false\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tresult.edges = append(result.edges, edge.key)\n\t\tresult.amount = append(result.amount, amount)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn result, true\n}\n\nfunc addReservation(dst map[edgeKey]lnwire.MilliSatoshi,\n\treservation routeReservation) {\n\n\tfor i, key := range reservation.edges {\n\t\tdst[key] += reservation.amount[i]\n\t}\n}\n\nfunc (r *candidateRouter) evaluateRoute(rt *route.Route,\n\treserved map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\treservation, ok := r.routeReservation(rt)\n\tif !ok {\n\t\treturn math.Inf(1)\n\t}\n\n\tscore := float64(routeFee(rt))\n\tfor i, key := range reservation.edges {\n\t\tedge := r.edgeByKey[key]\n\t\talready := reserved[key]\n\t\tload := reservation.amount[i] + already\n\t\tscore += r.edgeScore(edge, load, already)\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) remainingBudget() (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi, false\n\t}\n\tif r.spentFee >= r.spec.FeeLimitMsat {\n\t\treturn 0, true\n\t}\n\n\treturn r.spec.FeeLimitMsat - r.spentFee, true\n}\n\nfunc (r *candidateRouter) findBudgetedRoute(\n\tdeliver lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi, limited bool) (*route.Route, error) {\n\n\tweights := [...]float64{1, 4, 16, 64, 256, 4096, 1000000000}\n\n\tvar bestRoute *route.Route\n\tbestScore := math.Inf(1)\n\n\tfor _, weight := range weights {\n\t\trt, err := r.searchRoute(deliver, reserved, weight)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif limited && routeFee(rt) > feeCap {\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := r.evaluateRoute(rt, reserved)\n\t\tif score < bestScore {\n\t\t\tbestScore = score\n\t\t\tbestRoute = rt\n\t\t}\n\n\t\tif weight == 1 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, errors.New(\"no route within fee budget\")\n\t}\n\n\treturn bestRoute, nil\n}\n\nfunc shardFeeCap(budget, shard, remaining lnwire.MilliSatoshi,\n\tslots int) lnwire.MilliSatoshi {\n\n\tif slots <= 1 || shard >= remaining {\n\t\treturn budget\n\t}\n\n\tcap := budget * shard / remaining\n\tcap += budget / lnwire.MilliSatoshi(slots*5)\n\tif cap > budget {\n\t\tcap = budget\n\t}\n\n\treturn cap\n}\n\nfunc (r *candidateRouter) constructPlan(amount lnwire.MilliSatoshi,\n\tparts int, grow bool, baseReserved map[edgeKey]lnwire.MilliSatoshi,\n\tbudget lnwire.MilliSatoshi, limited bool) ([]*route.Route,\n\tfloat64, error) {\n\n\treserved := copyReservations(baseReserved)\n\tremaining := amount\n\tbudgetLeft := budget\n\tplan := make([]*route.Route, 0, parts)\n\tscore := 0.0\n\n\tfor i := 0; i < parts; i++ {\n\t\tslots := parts - i\n\t\tshard := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\t\tif shard < minShardMsat {\n\t\t\treturn nil, 0, errors.New(\"shard too small\")\n\t\t}\n\n\t\tcap := budgetLeft\n\t\tif limited {\n\t\t\tcap = shardFeeCap(\n\t\t\t\tbudgetLeft, shard, remaining, slots,\n\t\t\t)\n\t\t}\n\n\t\trt, err := r.findBudgetedRoute(\n\t\t\tshard, reserved, cap, limited,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\tif grow && slots > 1 {\n\t\t\tminFuture := shard / 2\n\t\t\tif minFuture < minShardMsat {\n\t\t\t\tminFuture = minShardMsat\n\t\t\t}\n\n\t\t\tupper := shard * 3 / 2\n\t\t\tleave := minFuture * lnwire.MilliSatoshi(slots-1)\n\t\t\tif remaining > leave && upper > remaining-leave {\n\t\t\t\tupper = remaining - leave\n\t\t\t}\n\n\t\t\tlow := shard\n\t\t\tbest := rt\n\t\t\tfor step := 0; step < 5 && upper > low; step++ {\n\t\t\t\ttrial := low + (upper-low+1)/2\n\t\t\t\ttrialCap := budgetLeft\n\t\t\t\tif limited {\n\t\t\t\t\ttrialCap = shardFeeCap(\n\t\t\t\t\t\tbudgetLeft, trial,\n\t\t\t\t\t\tremaining, slots,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tcandidate, findErr := r.findBudgetedRoute(\n\t\t\t\t\ttrial, reserved, trialCap, limited,\n\t\t\t\t)\n\t\t\t\tif findErr != nil {\n\t\t\t\t\tupper = trial - 1\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tbest = candidate\n\t\t\t\tlow = trial\n\t\t\t}\n\t\t\trt = best\n\t\t\tshard = routeDelivered(rt)\n\t\t}\n\n\t\tfee := routeFee(rt)\n\t\tif limited && fee > budgetLeft {\n\t\t\treturn nil, 0, errors.New(\"plan exceeds fee budget\")\n\t\t}\n\n\t\tscore += r.evaluateRoute(rt, reserved)\n\t\treservation, ok := r.routeReservation(rt)\n\t\tif !ok {\n\t\t\treturn nil, 0, errors.New(\"invalid planned route\")\n\t\t}\n\t\taddReservation(reserved, reservation)\n\n\t\tplan = append(plan, rt)\n\t\tremaining -= shard\n\t\tif limited {\n\t\t\tbudgetLeft -= fee\n\t\t}\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, 0, errors.New(\"incomplete shard plan\")\n\t}\n\n\tscore += float64(parts-1) * 20000\n\treturn plan, score, nil\n}\n\nfunc (r *candidateRouter) makePlan(amount lnwire.MilliSatoshi,\n\tpartsLeft uint32) ([]*route.Route, error) {\n\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tbaseReserved := r.currentReservations()\n\tbudget, limited := r.remainingBudget()\n\n\tvar best []*route.Route\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(partsLeft); parts++ {\n\t\tif amount/lnwire.MilliSatoshi(parts) < minShardMsat {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, grow := range []bool{false, true} {\n\t\t\tif parts == 1 && grow {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tplan, score, err := r.constructPlan(\n\t\t\t\tamount, parts, grow, baseReserved,\n\t\t\t\tbudget, limited,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif score < bestScore {\n\t\t\t\tbestScore = score\n\t\t\t\tbest = plan\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) reconcileHeld(inFlight uint32) {\n\tif uint32(len(r.held)) <= inFlight {\n\t\treturn\n\t}\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tstart := len(r.held) - int(inFlight)\n\tremaining := make([]routeReservation, int(inFlight))\n\tcopy(remaining, r.held[start:])\n\tr.held = remaining\n}\n\nfunc (r *candidateRouter) prepareAttempt(rt *route.Route) {\n\treservation, ok := r.routeReservation(rt)\n\tif !ok {\n\t\treturn\n\t}\n\n\treserved := r.currentReservations()\n\trecord := attemptRecord{\n\t\tedges: append([]edgeKey(nil), reservation.edges...),\n\t\tamount: append([]lnwire.MilliSatoshi(nil), reservation.amount...),\n\t\tloads: make([]lnwire.MilliSatoshi, len(reservation.edges)),\n\t}\n\tfor i, key := range reservation.edges {\n\t\trecord.loads[i] = reservation.amount[i] + reserved[key]\n\t}\n\n\tr.attempts[rt] = record\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\n\tr.reconcileHeld(inFlightHtlcs)\n\n\tfor len(r.plan) > 0 {\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tif routeDelivered(rt) > amt {\n\t\t\tr.plan = nil\n\t\t\tbreak\n\t\t}\n\n\t\tbudget, limited := r.remainingBudget()\n\t\tif limited && routeFee(rt) > budget {\n\t\t\tr.plan = nil\n\t\t\tbreak\n\t\t}\n\n\t\tr.prepareAttempt(rt)\n\t\treturn rt, nil\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tplan, err := r.makePlan(amt, maxParts-inFlightHtlcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.plan = plan\n\n\trt := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.prepareAttempt(rt)\n\n\treturn rt, nil\n}\n\nfunc failureOutgoingIndex(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) fallbackAttemptRecord(\n\trt *route.Route) attemptRecord {\n\n\treservation, ok := r.routeReservation(rt)\n\tif !ok {\n\t\treturn attemptRecord{}\n\t}\n\n\treserved := r.currentReservations()\n\trecord := attemptRecord{\n\t\tedges: reservation.edges,\n\t\tamount: reservation.amount,\n\t\tloads: make([]lnwire.MilliSatoshi, len(reservation.edges)),\n\t}\n\tfor i, key := range reservation.edges {\n\t\trecord.loads[i] = reservation.amount[i] + reserved[key]\n\t}\n\n\treturn record\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\trecord, ok := r.attempts[rt]\n\tif ok {\n\t\tdelete(r.attempts, rt)\n\t} else {\n\t\trecord = r.fallbackAttemptRecord(rt)\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range record.edges {\n\t\t\trecordSuccess(key, record.loads[i])\n\t\t\tdelete(r.recent, key)\n\t\t\tr.extra[key] *= 0.25\n\t\t}\n\n\t\tr.spentFee += routeFee(rt)\n\t\tif reservation, valid := r.routeReservation(rt); valid {\n\t\t\tr.held = append(r.held, reservation)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIdx := failureOutgoingIndex(rt, result.FailureSource)\n\tfor i := 0; i < failIdx && i < len(record.edges); i++ {\n\t\trecordSuccess(record.edges[i], record.loads[i])\n\t}\n\n\tliquidityFailure :=\n\t\tresult.Failure.Code() == lnwire.CodeTemporaryChannelFailure\n\n\tif failIdx >= 0 && failIdx < len(record.edges) {\n\t\tkey := record.edges[failIdx]\n\n\t\tif liquidityFailure {\n\t\t\trecordFailure(key, record.loads[failIdx])\n\n\t\t\trecent := r.recent[key]\n\t\t\trecent.amount = record.loads[failIdx]\n\t\t\tif recent.count < 20 {\n\t\t\t\trecent.count++\n\t\t\t}\n\t\t\tr.recent[key] = recent\n\t\t} else {\n\t\t\tr.extra[key] += 2_000_000\n\t\t}\n\t} else {\n\t\tfor _, key := range record.edges {\n\t\t\tr.extra[key] += 150_000\n\t\t}\n\t}\n\n\treturn nil\n}\n\nvar _ routing.SimRouter = (*candidateRouter)(nil)\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\"candidate-router(%x)\", r.source[:4])\n}"
}
},
{
"id": 6,
"parent": 0,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskPriceMsat = 420000.0\n\thopCostMsat = 12000.0\n\treuseCostMsat = 180000.0\n\tmaxSearchHops = 32\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat int64\n\tfeeRatePPM int64\n\n\tinboundBase int64\n\tinboundRate int64\n\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tfee := e.baseFeeMsat + int64(amt)*e.feeRatePPM/1_000_000\n\tif fee <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) inboundFee(amt lnwire.MilliSatoshi) int64 {\n\treturn e.inboundBase + int64(amt)*e.inboundRate/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(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 searchState struct {\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n\tscore float64\n\tdepth int\n\tnext *searchState\n}\n\ntype searchItem struct {\n\tstate *searchState\n\tindex int\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].state.score < q[j].state.score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *searchQueue) Push(value any) {\n\titem := value.(*searchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype searchLabel struct {\n\tamt lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype plannedRoute struct {\n\troute *route.Route\n\tscore float64\n\tfee lnwire.MilliSatoshi\n}\n\ntype attemptInfo struct {\n\tloads map[edgeKey]lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[edgeKey]liquidityBelief\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tpolicyBad map[edgeKey]uint32\n\tfailureCount map[edgeKey]uint32\n\n\tcommittedFee lnwire.MilliSatoshi\n\tpartsUsed uint32\n\tattempts uint32\n\n\tplan []*plannedRoute\n\toffered map[*route.Route]*attemptInfo\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpolicyBad: make(map[edgeKey]uint32),\n\t\tfailureCount: make(map[edgeKey]uint32),\n\t\toffered: make(map[*route.Route]*attemptInfo),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := 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\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(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: int64(policy.FeeBaseMSat),\n\t\t\t\t\tfeeRatePPM: int64(policy.FeeProportionalMillionths),\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key := range r.edges {\n\t\tif belief, ok := sharedBeliefs.values[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 addMSat(a, b lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {\n\tif b > lnwire.MaxMilliSatoshi-a {\n\t\treturn 0, false\n\t}\n\treturn a + b, true\n}\n\nfunc forwardingFee(inEdge, outEdge *candidateEdge,\n\toutAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := outEdge.fee(outAmt)\n\tinboundBasis, ok := addMSat(outAmt, outFee)\n\tif !ok {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\ttotal := int64(outFee) + inEdge.inboundFee(inboundBasis)\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\tif total > int64(lnwire.MaxMilliSatoshi) {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn lnwire.MilliSatoshi(total)\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(-x/0.035)\n\thighMode := 0.48 / (1 + math.Exp(17*(x-0.79)))\n\tp := lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif load > edge.capacity {\n\t\treturn 0\n\t}\n\tif edge.from == r.source {\n\t\treturn 0.999\n\t}\n\n\tprior := bimodalPrior(load, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\n\tif belief.upperBad > 0 && load >= belief.upperBad {\n\t\treturn 0.012\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && belief.upperBad > belief.lowerOK:\n\t\twidth := float64(belief.upperBad - belief.lowerOK)\n\t\tposition := float64(load-belief.lowerOK) / width\n\t\tevidence := 0.995*(1-position) + 0.012*position\n\t\treturn 0.82*evidence + 0.18*prior\n\n\tcase belief.upperBad > 0:\n\t\tposition := float64(load) / float64(belief.upperBad)\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\t\tevidence := 0.985*(1-position) + 0.012*position\n\t\treturn 0.70*evidence + 0.30*prior\n\n\tcase belief.lowerOK > 0:\n\t\tratio := float64(belief.lowerOK) / float64(load)\n\t\tif ratio > 1 {\n\t\t\tratio = 1\n\t\t}\n\t\treturn prior + (0.995-prior)*0.70*ratio\n\t}\n\n\treturn prior\n}\n\nfunc (r *candidateRouter) edgeCost(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) (float64, bool) {\n\n\tif !edge.usableHTLC(amt) {\n\t\treturn 0, false\n\t}\n\n\treserved := reservations[edge.key]\n\tload, ok := addMSat(amt, reserved)\n\tif !ok || load > edge.capacity {\n\t\treturn 0, false\n\t}\n\n\tif edge.from == r.source {\n\t\tbalance := r.localBalances[edge.chanID]\n\t\tif reserved > balance || amt > balance-reserved {\n\t\t\treturn 0, false\n\t\t}\n\t}\n\n\tp := r.probability(edge, load)\n\tif p <= 0 {\n\t\treturn 0, false\n\t}\n\n\tcost := -math.Log(p)*riskPriceMsat + hopCostMsat\n\tif reserved > 0 {\n\t\tfraction := float64(reserved) / float64(edge.capacity)\n\t\tcost += reuseCostMsat * (1 + 2*fraction)\n\t}\n\tif failures := r.failureCount[edge.key]; failures > 0 {\n\t\tcost += float64(failures) * 65000\n\t}\n\tif bad := r.policyBad[edge.key]; bad > 0 {\n\t\tcost += float64(bad) * 900000\n\t}\n\n\treturn cost, true\n}\n\nfunc stateContains(state *searchState, node route.Vertex) bool {\n\tfor current := state; current != nil; current = current.next {\n\t\tif current.edge.from == node || current.edge.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc acceptLabel(labels map[edgeKey][]searchLabel, key edgeKey,\n\tamt lnwire.MilliSatoshi, score float64) bool {\n\n\tcurrent := labels[key]\n\tfor _, label := range current {\n\t\tif label.amt <= amt && label.score <= score {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfiltered := current[:0]\n\tfor _, label := range current {\n\t\tif amt <= label.amt && score <= label.score {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, label)\n\t}\n\n\tfiltered = append(filtered, searchLabel{amt: amt, score: score})\n\tif len(filtered) > 5 {\n\t\tworst := 0\n\t\tfor i := 1; i < len(filtered); i++ {\n\t\t\tif filtered[i].score > filtered[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\t\tfiltered[worst] = filtered[len(filtered)-1]\n\t\tfiltered = filtered[:len(filtered)-1]\n\t}\n\tlabels[key] = filtered\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(deliver,\n\tfeeBudget lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid shard amount\")\n\t}\n\n\tpq := &searchQueue{}\n\theap.Init(pq)\n\tlabels := make(map[edgeKey][]searchLabel)\n\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tcost, ok := r.edgeCost(edge, deliver, reservations)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tstate := &searchState{\n\t\t\tedge: edge,\n\t\t\tamt: deliver,\n\t\t\tscore: cost,\n\t\t\tdepth: 1,\n\t\t}\n\t\tif acceptLabel(labels, edge.key, deliver, cost) {\n\t\t\theap.Push(pq, &searchItem{state: state})\n\t\t}\n\t}\n\n\tfor pq.Len() > 0 {\n\t\tstate := heap.Pop(pq).(*searchItem).state\n\n\t\tif state.edge.from == r.source {\n\t\t\tfee := state.amt - deliver\n\t\t\tif fee <= feeBudget {\n\t\t\t\trt, err := r.buildRoute(deliver, state)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn &plannedRoute{\n\t\t\t\t\troute: rt,\n\t\t\t\t\tscore: state.score,\n\t\t\t\t\tfee: fee,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif state.depth >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tnode := state.edge.from\n\t\tfor _, inEdge := range r.incomingEdges[node] {\n\t\t\tif stateContains(state, inEdge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnodeFee := forwardingFee(inEdge, state.edge, state.amt)\n\t\t\tif nodeFee == lnwire.MaxMilliSatoshi {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tinAmt, ok := addMSat(state.amt, nodeFee)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeCost, ok := r.edgeCost(inEdge, inAmt, reservations)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := state.score + float64(nodeFee) + edgeCost\n\t\t\tif !acceptLabel(labels, inEdge.key, inAmt, score) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tedge: inEdge,\n\t\t\t\tamt: inAmt,\n\t\t\t\tscore: score,\n\t\t\t\tdepth: state.depth + 1,\n\t\t\t\tnext: state,\n\t\t\t}\n\t\t\theap.Push(pq, &searchItem{state: next})\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tfirst *searchState) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor state := first; state != nil; state = state.next {\n\t\tpath = append(path, state.edge)\n\t}\n\tif len(path) == 0 || path[0].from != r.source ||\n\t\tpath[len(path)-1].to != r.spec.Target {\n\n\t\treturn nil, errors.New(\"broken route path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = deliver\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := forwardingFee(path[i], path[i+1], amounts[i+1])\n\t\tvar ok bool\n\t\tamounts[i], ok = addMSat(amounts[i+1], fee)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tamtToForward := deliver\n\t\toutgoingExpiry := finalCltvDelta\n\t\tif i+1 < n {\n\t\t\tamtToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc copyReservations(source map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\tresult := make(map[edgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc channelAmount(rt *route.Route, index int) lnwire.MilliSatoshi {\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc reserveRoute(reservations map[edgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tamount := channelAmount(rt, i)\n\t\treservations[key], _ = addMSat(reservations[key], amount)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc remainingPlanAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, item := range plan {\n\t\tlast := item.route.Hops[len(item.route.Hops)-1]\n\t\ttotal, _ = addMSat(total, last.AmtToForward)\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFee >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFee\n}\n\nfunc (r *candidateRouter) constructPlan(total lnwire.MilliSatoshi,\n\tpartCount int, unequal bool) ([]*plannedRoute, float64, bool) {\n\n\treservations := copyReservations(r.reserved)\n\tfeeLeft := r.remainingFeeBudget()\n\tremaining := total\n\tplan := make([]*plannedRoute, 0, partCount)\n\tscore := 0.0\n\n\tfor i := 0; i < partCount; i++ {\n\t\tslots := partCount - i\n\t\tshard := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tif unequal && slots > 1 {\n\t\t\tminTail := shard / 3\n\t\t\tif minTail < 1000 {\n\t\t\t\tminTail = 1000\n\t\t\t}\n\n\t\t\ttailNeed := minTail * lnwire.MilliSatoshi(slots-1)\n\t\t\tif tailNeed < remaining {\n\t\t\t\thigh := remaining - tailNeed\n\t\t\t\tif high > shard {\n\t\t\t\t\tcandidates := []lnwire.MilliSatoshi{\n\t\t\t\t\t\thigh,\n\t\t\t\t\t\tshard + (high-shard)*2/3,\n\t\t\t\t\t\tshard + (high-shard)/3,\n\t\t\t\t\t}\n\t\t\t\t\tfor _, candidate := range candidates {\n\t\t\t\t\t\ttest, err := r.findRoute(\n\t\t\t\t\t\t\tcandidate, feeLeft, reservations,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\tshard = candidate\n\t\t\t\t\t\t\ttest = nil\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif slots == 1 {\n\t\t\tshard = remaining\n\t\t}\n\n\t\titem, err := r.findRoute(shard, feeLeft, reservations)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tplan = append(plan, item)\n\t\treserveRoute(reservations, item.route)\n\t\tremaining -= shard\n\t\tif feeLeft != lnwire.MaxMilliSatoshi {\n\t\t\tif item.fee > feeLeft {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tfeeLeft -= item.fee\n\t\t}\n\n\t\tscore += item.score\n\t\tif i > 0 {\n\t\t\tscore += 35000\n\t\t}\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, 0, false\n\t}\n\treturn plan, score, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tpartsAvailable uint32) error {\n\n\tif partsAvailable == 0 {\n\t\treturn errors.New(\"maximum parts reached\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor count := 1; count <= int(partsAvailable); count++ {\n\t\tequal, score, ok := r.constructPlan(total, count, false)\n\t\tif ok && score < bestScore {\n\t\t\tbest = equal\n\t\t\tbestScore = score\n\t\t}\n\n\t\tif count > 1 {\n\t\t\tunequal, unequalScore, ok := r.constructPlan(\n\t\t\t\ttotal, count, true,\n\t\t\t)\n\t\t\tif ok && unequalScore < bestScore {\n\t\t\t\tbest = unequal\n\t\t\t\tbestScore = unequalScore\n\t\t\t}\n\t\t}\n\n\t\tif count >= 8 && best != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn errors.New(\"no route found\")\n\t}\n\n\tr.plan = best\n\treturn nil\n}\n\nfunc (r *candidateRouter) attemptInfo(rt *route.Route) *attemptInfo {\n\tinfo := &attemptInfo{\n\t\tloads: make(map[edgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tamount := channelAmount(rt, i)\n\t\tload, ok := addMSat(amount, r.reserved[key])\n\t\tif !ok {\n\t\t\tload = lnwire.MaxMilliSatoshi\n\t\t}\n\t\tinfo.loads[key] = load\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn info\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 complete\")\n\t}\n\n\tmaxAttempts := uint32(48) + 8*r.spec.MaxParts\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\n\tif len(r.plan) > 0 && remainingPlanAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tused := r.partsUsed\n\t\tif inFlightHtlcs > used {\n\t\t\tused = inFlightHtlcs\n\t\t}\n\t\tif used >= r.spec.MaxParts {\n\t\t\treturn nil, errors.New(\"maximum parts reached\")\n\t\t}\n\n\t\tif err := r.makePlan(amt, r.spec.MaxParts-used); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\titem := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.offered[item.route] = r.attemptInfo(item.route)\n\treturn item.route, nil\n}\n\nfunc routeEdgeKeys(rt *route.Route) []edgeKey {\n\tkeys := make([]edgeKey, len(rt.Hops))\n\tfrom := rt.SourcePubKey\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\tfrom = hop.PubKeyBytes\n\t}\n\treturn keys\n}\n\nfunc failureOutgoingIndex(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 failureCode(failure any) (lnwire.FailCode, bool) {\n\tcoded, ok := failure.(interface {\n\t\tCode() lnwire.FailCode\n\t})\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn coded.Code(), true\n}\n\nfunc (r *candidateRouter) storeBelief(key edgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tshared := sharedBeliefs.values[key]\n\tif belief.lowerOK > shared.lowerOK {\n\t\tshared.lowerOK = belief.lowerOK\n\t}\n\tif belief.upperBad > 0 &&\n\t\t(shared.upperBad == 0 || belief.upperBad < shared.upperBad) {\n\n\t\tshared.upperBad = belief.upperBad\n\t}\n\tif shared.upperBad > 0 && shared.lowerOK >= shared.upperBad {\n\t\tshared.upperBad = 0\n\t}\n\tsharedBeliefs.values[key] = shared\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif load > belief.lowerOK {\n\t\tbelief.lowerOK = load\n\t}\n\tif belief.upperBad > 0 && belief.lowerOK >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad == 0 || load < belief.upperBad {\n\t\tbelief.upperBad = load\n\t}\n\tif belief.lowerOK >= belief.upperBad {\n\t\tbelief.lowerOK = 0\n\t}\n\tr.failureCount[key]++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tr.attempts++\n\n\tinfo := r.offered[rt]\n\tdelete(r.offered, rt)\n\tif info == nil {\n\t\tinfo = r.attemptInfo(rt)\n\t}\n\n\tkeys := routeEdgeKeys(rt)\n\n\tif result.Failure == nil {\n\t\tfor _, key := range keys {\n\t\t\tr.recordSuccess(key, info.loads[key])\n\t\t}\n\n\t\treserveRoute(r.reserved, rt)\n\t\tr.partsUsed++\n\n\t\tif len(rt.Hops) == 0 {\n\t\t\treturn fmt.Errorf(\"successful route has no hops\")\n\t\t}\n\t\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\tif rt.TotalAmount >= delivered {\n\t\t\tfee := rt.TotalAmount - delivered\n\t\t\tr.committedFee, _ = addMSat(r.committedFee, fee)\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tfailIndex := failureOutgoingIndex(rt, result.FailureSource)\n\tcode, hasCode := failureCode(result.Failure)\n\n\tif failIndex >= 0 {\n\t\tfor i := 0; i < failIndex && i < len(keys); i++ {\n\t\t\tr.recordSuccess(keys[i], info.loads[keys[i]])\n\t\t}\n\t}\n\n\tif !hasCode || failIndex < 0 || failIndex >= len(keys) {\n\t\treturn nil\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(\n\t\t\tkeys[failIndex], info.loads[keys[failIndex]],\n\t\t)\n\n\tcase lnwire.CodeFeeInsufficient, lnwire.CodeIncorrectCltvExpiry:\n\t\tr.policyBad[keys[failIndex]]++\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 7,
"parent": 0,
"score": 0.3036,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(30_000)\n\thopCostMsat = float64(2_000)\n\treusedEdgeCostMsat = float64(650_000)\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 8\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[edgeKey]float64\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\tplanRemaining lnwire.MilliSatoshi\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\tsuccessfulParts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() {\n\t\treturn 0\n\t}\n\tif now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\t// Evidence softens over roughly half an hour of virtual time. Bounds\n\t// remain strong within a payment but do not permanently retire a\n\t// corridor when traffic is moving.\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\t// A failure may have been caused by a sibling reservation, so an\n\t\t// upper bound is deliberately soft rather than a permanent ban.\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc cloneReservations(src map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\t// Our balance is known exactly, but retain a little\n\t\t\t\t// uncertainty for concurrent sibling payments.\n\t\t\t\tp = 0.985\n\t\t\t}\n\n\t\t\tscore := cur.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.45\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tint64(total) * weight / sum,\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{45, 55}, {55, 45},\n\t\t\t{35, 65}, {65, 35},\n\t\t\t{25, 75}, {75, 25},\n\t\t} {\n\t\t\tpatterns = append(patterns,\n\t\t\t\tweightedAllocation(total, weights))\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{20, 30, 50}, {50, 30, 20},\n\t\t\t{25, 35, 40}, {40, 35, 25},\n\t\t} {\n\t\t\tpatterns = append(patterns,\n\t\t\t\tweightedAllocation(total, weights))\n\t\t}\n\n\tcase 4:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{15, 20, 25, 40}, {40, 25, 20, 15},\n\t\t} {\n\t\t\tpatterns = append(patterns,\n\t\t\t\tweightedAllocation(total, weights))\n\t\t}\n\n\tdefault:\n\t\tif parts > 4 {\n\t\t\tascending := make([]int64, parts)\n\t\t\tdescending := make([]int64, parts)\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tascending[i] = int64(i + 1)\n\t\t\t\tdescending[i] = int64(parts - i)\n\t\t\t}\n\t\t\tpatterns = append(patterns,\n\t\t\t\tweightedAllocation(total, ascending),\n\t\t\t\tweightedAllocation(total, descending),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, remainingBudget)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t}\n\n\t// Repeated base fees and the larger failure surface make unnecessary\n\t// parts undesirable, while reliability can still justify splitting.\n\ttotalScore += float64(len(plan)-1) * 40_000\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) != 0 {\n\t\treturn best, nil\n\t}\n\n\t// If joint planning cannot cover the full remainder, retain a bounded\n\t// reactive fallback. It retries at a smaller amount, but only when\n\t// enough part slots remain to make such a shard useful.\n\tif slots > 1 {\n\t\tshard := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tfor shard >= 10_000 {\n\t\t\tcap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tcap = feeBudget * shard / total\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\t\t\tshard /= 2\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tused := r.successfulParts\n\tif inFlightHtlcs > used {\n\t\tused = inFlightHtlcs\n\t}\n\tif used >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - used\n\n\tif len(r.plan) != 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t\tr.planRemaining = 0\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t\tr.planRemaining = queuedAmount(plan)\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.planRemaining -= next.meta.delivered\n\tr.issued[next.rt] = next.meta\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\tr.planRemaining = 0\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.35\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.successfulParts++\n\t\treturn nil\n\t}\n\n\t// Any failed route invalidates the remainder of the joint plan because\n\t// its tentative reservations assumed that this shard succeeded.\n\tr.plan = nil\n\tr.planRemaining = 0\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Every edge before the failing node's outgoing edge demonstrably\n\t// carried the total load that included our existing reservations.\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tcode := result.Failure.Code()\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.edgePenalty[edge.key] += 350_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\t// These indicate a policy mismatch rather than hidden\n\t\t\t// liquidity. Avoid the edge without poisoning its balance.\n\t\t\tr.edgePenalty[edge.key] += 5_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 1_500_000\n\t\t}\n\t} else {\n\t\t// A dispatch refusal has no failure source and teaches nothing\n\t\t// about liquidity. Add a small budget guard and path penalty so\n\t\t// an accounting discrepancy cannot burn the attempt budget.\n\t\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\t\tguard := meta.fee / 20\n\t\t\tif guard < 1_000 {\n\t\t\t\tguard = 1_000\n\t\t\t}\n\t\t\tr.budgetGuard += guard\n\t\t}\n\t\tfor _, edge := range meta.path {\n\t\t\tr.edgePenalty[edge.key] += 100_000\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 8,
"parent": 0,
"score": 0.3858,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskPriceMsat = 420000.0\n\thopPriceMsat = 20000.0\n\tpartPriceMsat = 80000.0\n\treusePriceMsat = 850000.0\n\tfailurePriceMsat = 400000.0\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tbase := int64(e.baseFeeMsat)\n\trate := int64(e.feeRatePPM)\n\tfee := base + int64(amt)*rate/1_000_000\n\tif fee <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) policyUsable(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\n\treturn amt <= e.capacity\n}\n\n// forwardingFee computes the fee charged by the node between incoming and\n// outgoing. The inbound fee is attached to the incoming channel, and is\n// applied to the forwarded amount plus the ordinary outbound fee.\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := outgoing.fee(amt)\n\tinBase := int64(incoming.inboundBase)\n\tinRate := int64(incoming.inboundRate)\n\tinFee := inBase +\n\t\tint64(amt+outFee)*inRate/1_000_000\n\n\ttotal := int64(outFee) + inFee\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\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.RWMutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\nfunc readBelief(key edgeKey) liquidityBelief {\n\tsharedBeliefs.RLock()\n\tb := sharedBeliefs.m[key]\n\tsharedBeliefs.RUnlock()\n\n\treturn b\n}\n\nfunc recordSuccess(edge *candidateEdge, amt lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tb := sharedBeliefs.m[edge.key]\n\n\tif amt > b.lowerOK {\n\t\tb.lowerOK = amt\n\t}\n\tif b.upperFail != 0 && amt >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\n\toptimistic := edge.capacity * 92 / 100\n\tif optimistic < amt {\n\t\toptimistic = amt\n\t}\n\tif optimistic > b.estimate {\n\t\tb.estimate = optimistic\n\t}\n\n\tif b.samples < 1000 {\n\t\tb.samples++\n\t}\n\tsharedBeliefs.m[edge.key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc recordFailure(edge *candidateEdge, amt lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tb := sharedBeliefs.m[edge.key]\n\n\tif b.upperFail == 0 || amt < b.upperFail {\n\t\tb.upperFail = amt\n\t}\n\tif b.lowerOK >= b.upperFail {\n\t\tb.lowerOK = b.upperFail * 3 / 4\n\t}\n\n\tdepleted := amt / 5\n\tfloor := edge.capacity / 200\n\tif depleted < floor {\n\t\tdepleted = floor\n\t}\n\tif b.estimate == 0 || depleted < b.estimate {\n\t\tb.estimate = depleted\n\t}\n\n\tif b.samples < 1000 {\n\t\tb.samples++\n\t}\n\tsharedBeliefs.m[edge.key] = b\n\tsharedBeliefs.Unlock()\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\n// bimodalPrior models a channel as usually being close to empty or close to\n// full in a particular direction. Tiny transfers can use either mode, while\n// transfers near capacity require the full mode and encounter a sharp cliff.\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity == 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\tfullMode := 1.0 / (1.0 + math.Exp((x-0.93)/0.035))\n\n\treturn clampProbability(0.48*lowMode + 0.52*fullMode)\n}\n\nfunc edgeProbability(edge *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := bimodalPrior(load, edge.capacity)\n\tb := readBelief(edge.key)\n\tif b.samples == 0 {\n\t\treturn prior\n\t}\n\n\tif b.lowerOK != 0 && load <= b.lowerOK {\n\t\treturn 0.985\n\t}\n\tif b.upperFail != 0 && load >= b.upperFail {\n\t\t// A failure may have been caused by a temporarily held sibling\n\t\t// shard, so never permanently retire the direction.\n\t\treturn 0.012\n\t}\n\n\tevidence := prior\n\tif b.estimate != 0 && edge.capacity != 0 {\n\t\twidth := 0.055 * float64(edge.capacity)\n\t\tif width < 1 {\n\t\t\twidth = 1\n\t\t}\n\t\tevidence = 1.0 / (1.0 +\n\t\t\tmath.Exp((float64(load)-float64(b.estimate))/width))\n\t}\n\n\tconfidence := 0.18 * float64(b.samples)\n\tif confidence > 0.85 {\n\t\tconfidence = 0.85\n\t}\n\tp := prior*(1-confidence) + evidence*confidence\n\n\tif b.lowerOK != 0 && b.upperFail != 0 &&\n\t\tb.lowerOK < load && load < b.upperFail {\n\n\t\tspan := float64(b.upperFail - b.lowerOK)\n\t\tposition := float64(load-b.lowerOK) / span\n\t\tbounded := 0.985*(1-position) + 0.012*position\n\t\tp = 0.45*p + 0.55*bounded\n\t}\n\n\treturn clampProbability(p)\n}\n\ntype failurePenalty struct {\n\tamt lnwire.MilliSatoshi\n\tcount uint32\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\tcommittedFee lnwire.MilliSatoshi\n\n\t// held is the aggregate load of this payment's successful atomic\n\t// shards. heldRoutes lets it be reconciled with inFlightHtlcs.\n\theld map[edgeKey]lnwire.MilliSatoshi\n\theldRoutes []*route.Route\n\n\t// plan contains the rest of an up-front route set. A failed shard\n\t// invalidates the set and triggers a new joint plan.\n\tplan []*route.Route\n\n\t// issued records the reservations that existed when a route was sent,\n\t// so feedback is learned at total load rather than shard load.\n\tissued map[*route.Route]map[edgeKey]lnwire.MilliSatoshi\n\n\tpenalties map[edgeKey]failurePenalty\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\theld: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route]map[edgeKey]lnwire.MilliSatoshi),\n\t\tpenalties: make(map[edgeKey]failurePenalty),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: edgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\ntype pathItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n\tidx int\n}\n\ntype pathQueue []*pathItem\n\nfunc (q pathQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q pathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q pathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *pathQueue) Push(x any) {\n\titem := x.(*pathItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *pathQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\n\treturn item\n}\n\nfunc copyReservations(src map[edgeKey]lnwire.MilliSatoshi) map[\n\tedgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingBudget() lnwire.MilliSatoshi {\n\tlimit := r.spec.FeeLimitMsat\n\tif limit == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFee >= limit {\n\t\treturn 0\n\t}\n\n\treturn limit - r.committedFee\n}\n\nfunc addWithinLimit(a, b, limit lnwire.MilliSatoshi) bool {\n\tif limit == lnwire.MaxMilliSatoshi {\n\t\treturn true\n\t}\n\tif a > limit {\n\t\treturn false\n\t}\n\n\treturn b <= limit-a\n}\n\nfunc (r *candidateRouter) penaltyFor(key edgeKey,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tp, ok := r.penalties[key]\n\tif !ok || p.amt == 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(load) / float64(p.amt)\n\tif ratio > 1 {\n\t\tratio = 1\n\t}\n\tif ratio < 0 {\n\t\tratio = 0\n\t}\n\n\treturn failurePriceMsat * float64(p.count) *\n\t\tratio * ratio * ratio\n}\n\n// findRoute performs a reliability-aware backward search. Its score is\n// denominated in millisatoshis: actual fees plus a fixed msat price for\n// negative log success probability.\nfunc (r *candidateRouter) findRoute(\n\tamt, maxFee lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) (*route.Route,\n\tfloat64, error) {\n\n\tif amt == 0 {\n\t\treturn nil, 0, errors.New(\"zero shard\")\n\t}\n\n\tbestScore := make(map[route.Vertex]float64)\n\tbestAmt := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tbestAmt[r.spec.Target] = amt\n\n\tpq := &pathQueue{}\n\theap.Push(pq, &pathItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*pathItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.001 {\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\tsendAmt := item.amt\n\t\t\tnodeFee := lnwire.MilliSatoshi(0)\n\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\toutgoing, ok := next[item.node]\n\t\t\t\tif !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnodeFee = forwardingFee(\n\t\t\t\t\tedge, outgoing, item.amt,\n\t\t\t\t)\n\t\t\t\tsendAmt += nodeFee\n\t\t\t}\n\n\t\t\tif !edge.policyUsable(sendAmt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\tif reserved > edge.capacity ||\n\t\t\t\tsendAmt > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tload := sendAmt + reserved\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || reserved > balance ||\n\t\t\t\t\tsendAmt > balance-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpartialFee := sendAmt - amt\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi &&\n\t\t\t\tpartialFee > maxFee {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := edgeProbability(edge, load)\n\t\t\tif edge.from == r.source {\n\t\t\t\t// The local balance is known exactly for this\n\t\t\t\t// payment's snapshot.\n\t\t\t\tprobability = 0.999\n\t\t\t}\n\n\t\t\tincrement := float64(nodeFee) +\n\t\t\t\triskPriceMsat*(-math.Log(probability)) +\n\t\t\t\thopPriceMsat +\n\t\t\t\tr.penaltyFor(edge.key, load)\n\n\t\t\tif reserved != 0 {\n\t\t\t\tfraction := float64(reserved) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tincrement += reusePriceMsat *\n\t\t\t\t\t(0.35 + fraction)\n\t\t\t}\n\n\t\t\tscore := item.score + increment\n\t\t\told, exists := bestScore[edge.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.from] = score\n\t\t\tbestAmt[edge.from] = sendAmt\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &pathItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: score,\n\t\t\t\tamt: sendAmt,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := bestScore[r.source]\n\tif !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\tfee := routeFee(rt)\n\tif maxFee != lnwire.MaxMilliSatoshi && fee > maxFee {\n\t\treturn nil, 0, errors.New(\"no route within fee budget\")\n\t}\n\n\treturn rt, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(\n\tamt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn 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\tfee := forwardingFee(\n\t\t\tpath[i], path[i+1], amtOver[i+1],\n\t\t)\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforwardAmt := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforwardAmt = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forwardAmt,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tdelivered := deliveredAmount(rt)\n\tif rt == nil || rt.TotalAmount < delivered {\n\t\treturn 0\n\t}\n\n\treturn rt.TotalAmount - delivered\n}\n\nfunc routeEdgeAmount(rt *route.Route, idx int) lnwire.MilliSatoshi {\n\tif idx == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[idx-1].AmtToForward\n}\n\nfunc routeEdgeKey(rt *route.Route, idx int) edgeKey {\n\tfrom := rt.SourcePubKey\n\tif idx > 0 {\n\t\tfrom = rt.Hops[idx-1].PubKeyBytes\n\t}\n\n\treturn edgeKey{\n\t\tchanID: rt.Hops[idx].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc reserveRoute(\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey := routeEdgeKey(rt, i)\n\t\treservations[key] += routeEdgeAmount(rt, i)\n\t}\n}\n\nfunc allocation(\n\ttotal lnwire.MilliSatoshi, parts, mode int) []lnwire.MilliSatoshi {\n\n\tweights := make([]uint64, parts)\n\tvar weightTotal uint64\n\n\tfor i := 0; i < parts; i++ {\n\t\tleft := uint64(parts - i)\n\t\tswitch mode {\n\t\tcase 1:\n\t\t\tweights[i] = left\n\t\tcase 2:\n\t\t\tweights[i] = left * left\n\t\tdefault:\n\t\t\tweights[i] = 1\n\t\t}\n\t\tweightTotal += weights[i]\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, parts)\n\tremainingAmt := total\n\tremainingWeight := weightTotal\n\n\tfor i := 0; i < parts; i++ {\n\t\tif i == parts-1 {\n\t\t\tresult[i] = remainingAmt\n\t\t\tbreak\n\t\t}\n\n\t\tvalue := lnwire.MilliSatoshi(\n\t\t\tuint64(remainingAmt) * weights[i] /\n\t\t\t\tremainingWeight,\n\t\t)\n\t\tif value == 0 {\n\t\t\tvalue = 1\n\t\t}\n\t\tresult[i] = value\n\t\tremainingAmt -= value\n\t\tremainingWeight -= weights[i]\n\t}\n\n\treturn result\n}\n\n// buildPlan compares complete route sets with equal, moderately unequal and\n// strongly unequal allocations. Each route is priced against the shards\n// already placed in the same hypothetical set.\nfunc (r *candidateRouter) buildPlan(\n\tremaining lnwire.MilliSatoshi, slots uint32) ([]*route.Route, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tmaxParts := int(slots)\n\tif maxParts > 20 {\n\t\tmaxParts = 20\n\t}\n\n\tbudget := r.remainingBudget()\n\tvar bestPlan []*route.Route\n\tbestPlanScore := math.Inf(1)\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tfor mode := 0; mode < 3; mode++ {\n\t\t\tamounts := allocation(remaining, parts, mode)\n\t\t\treservations := copyReservations(r.held)\n\t\t\tbudgetLeft := budget\n\t\t\tplan := make([]*route.Route, 0, parts)\n\t\t\ttotalScore := partPriceMsat * float64(parts)\n\t\t\tvalid := true\n\n\t\t\tfor _, shard := range amounts {\n\t\t\t\trt, score, err := r.findRoute(\n\t\t\t\t\tshard, budgetLeft, reservations,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\tvalid = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tfee := routeFee(rt)\n\t\t\t\tif !addWithinLimit(\n\t\t\t\t\tfee, 0, budgetLeft,\n\t\t\t\t) {\n\t\t\t\t\tvalid = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif budgetLeft != lnwire.MaxMilliSatoshi {\n\t\t\t\t\tbudgetLeft -= fee\n\t\t\t\t}\n\n\t\t\t\tplan = append(plan, rt)\n\t\t\t\ttotalScore += score\n\t\t\t\treserveRoute(reservations, rt)\n\t\t\t}\n\n\t\t\tif valid && totalScore < bestPlanScore {\n\t\t\t\tbestPlanScore = totalScore\n\t\t\t\tbestPlan = plan\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(bestPlan) != 0 {\n\t\treturn bestPlan, nil\n\t}\n\n\t// A complete greedy set can occasionally fail because its first path\n\t// consumed a corridor needed later. Preserve a lower-amount retry\n\t// escape hatch rather than terminally abandoning the payment.\n\tminimum := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\tcandidates := []lnwire.MilliSatoshi{\n\t\tremaining,\n\t\tremaining * 3 / 4,\n\t\tremaining * 2 / 3,\n\t\tremaining / 2,\n\t\tminimum,\n\t}\n\n\tvar fallback *route.Route\n\tfallbackScore := math.Inf(1)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tfor _, shard := range candidates {\n\t\tif shard < minimum {\n\t\t\tshard = minimum\n\t\t}\n\t\tif shard == 0 || shard > remaining || seen[shard] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[shard] = true\n\n\t\trt, score, err := r.findRoute(\n\t\t\tshard, budget, r.held,\n\t\t)\n\t\tif err == nil && score < fallbackScore {\n\t\t\tfallback = rt\n\t\t\tfallbackScore = score\n\t\t}\n\t}\n\n\tif fallback == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn []*route.Route{fallback}, nil\n}\n\nfunc (r *candidateRouter) rebuildHeld(count uint32) {\n\tif int(count) >= len(r.heldRoutes) {\n\t\treturn\n\t}\n\n\tif count == 0 {\n\t\tr.heldRoutes = nil\n\t\tr.held = make(map[edgeKey]lnwire.MilliSatoshi)\n\t\tr.plan = nil\n\t\treturn\n\t}\n\n\tr.heldRoutes = r.heldRoutes[:count]\n\tr.held = make(map[edgeKey]lnwire.MilliSatoshi)\n\tfor _, rt := range r.heldRoutes {\n\t\treserveRoute(r.held, rt)\n\t}\n\tr.plan = nil\n}\n\nfunc (r *candidateRouter) issue(rt *route.Route) *route.Route {\n\tr.issued[rt] = copyReservations(r.held)\n\treturn rt\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 complete\")\n\t}\n\n\tr.rebuildHeld(inFlightHtlcs)\n\n\tfor len(r.plan) != 0 {\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tif deliveredAmount(rt) > amt {\n\t\t\tr.plan = nil\n\t\t\tbreak\n\t\t}\n\t\tif routeFee(rt) > r.remainingBudget() {\n\t\t\tr.plan = nil\n\t\t\tbreak\n\t\t}\n\n\t\treturn r.issue(rt), nil\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tplan, err := r.buildPlan(amt, maxParts-inFlightHtlcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.plan = plan[1:]\n\t}\n\n\treturn r.issue(rt), nil\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\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(\n\tattemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tsnapshot, ok := r.issued[rt]\n\tif !ok {\n\t\tsnapshot = copyReservations(r.held)\n\t}\n\tdelete(r.issued, rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tedge := r.edges[key]\n\t\t\tif edge == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := routeEdgeAmount(rt, i) + snapshot[key]\n\t\t\trecordSuccess(edge, load)\n\n\t\t\tif penalty, exists := r.penalties[key]; exists {\n\t\t\t\tif penalty.count <= 1 {\n\t\t\t\t\tdelete(r.penalties, key)\n\t\t\t\t} else {\n\t\t\t\t\tpenalty.count--\n\t\t\t\t\tr.penalties[key] = penalty\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tr.committedFee += routeFee(rt)\n\t\tr.heldRoutes = append(r.heldRoutes, rt)\n\t\treserveRoute(r.held, rt)\n\n\t\treturn nil\n\t}\n\n\t// A planned sibling may depend on the route that just failed.\n\tr.plan = nil\n\n\tfailIdx := r.failureIndex(rt, result.FailureSource)\n\tif failIdx < 0 {\n\t\t// Dispatcher-side refusals provide no network evidence.\n\t\treturn nil\n\t}\n\n\t// Every edge before the failing node's outgoing channel carried the\n\t// HTLC successfully and therefore supplies a lower liquidity bound.\n\tpassCount := failIdx\n\tif passCount > len(rt.Hops) {\n\t\tpassCount = len(rt.Hops)\n\t}\n\tfor i := 0; i < passCount; i++ {\n\t\tkey := routeEdgeKey(rt, i)\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tload := routeEdgeAmount(rt, i) + snapshot[key]\n\t\trecordSuccess(edge, load)\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\treturn nil\n\t}\n\n\tkey := routeEdgeKey(rt, failIdx)\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn nil\n\t}\n\n\tload := routeEdgeAmount(rt, failIdx) + snapshot[key]\n\tpenalty := r.penalties[key]\n\tif penalty.amt == 0 || load < penalty.amt {\n\t\tpenalty.amt = load\n\t}\n\tif penalty.count < 12 {\n\t\tpenalty.count++\n\t}\n\tr.penalties[key] = penalty\n\n\tif result.Failure.Code() == lnwire.CodeTemporaryChannelFailure {\n\t\trecordFailure(edge, load)\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 9,
"parent": 0,
"score": 0.2035,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxSearchHops = 20\n\tmaxNodeLabels = 6\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype edgeRef struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n\tcapacity lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tfee := int64(e.baseFeeMsat) +\n\t\tint64(amt)*int64(e.feeRatePPM)/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) inboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tfee := e.inboundBase + int64(amt)*e.inboundRate/1_000_000\n\treturn lnwire.MilliSatoshi(fee)\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 plannedRoute struct {\n\trt *route.Route\n\tprior map[edgeKey]lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByRef map[edgeRef]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route]map[edgeKey]lnwire.MilliSatoshi\n\tplans []plannedRoute\n\n\tfailedHere map[edgeKey]lnwire.MilliSatoshi\n\tpolicyBad map[edgeKey]bool\n\n\tcommittedFee lnwire.MilliSatoshi\n\taccounted map[*route.Route]bool\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tbalances := make(map[uint64]lnwire.MilliSatoshi, len(localBalances))\n\tfor id, balance := range localBalances {\n\t\tbalances[id] = balance\n\t}\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByRef: make(map[edgeRef]*candidateEdge),\n\t\tlocalBalances: balances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route]map[edgeKey]lnwire.MilliSatoshi),\n\t\tfailedHere: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpolicyBad: make(map[edgeKey]bool),\n\t\taccounted: make(map[*route.Route]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tcapacity := lnwire.NewMSatFromSatoshis(ch.Capacity)\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeByRef[edgeRef{\n\t\t\t\t\tchanID: edge.chanID,\n\t\t\t\t\tfrom: edge.from,\n\t\t\t\t\tto: edge.to,\n\t\t\t\t}] = edge\n\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tb := sharedBeliefs.m[key]\n\t\t\t\tif b.capacity == 0 {\n\t\t\t\t\tb.capacity = capacity\n\t\t\t\t\tb.estimate = capacity / 2\n\t\t\t\t\tsharedBeliefs.m[key] = b\n\t\t\t\t}\n\t\t\t\tsharedBeliefs.Unlock()\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\toutAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := outgoing.fee(outAmt)\n\tinFee := incoming.inboundFee(outAmt + outFee)\n\ttotal := int64(outFee) + int64(inFee)\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\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.55 * math.Exp(-8*x)\n\thighMode := 0.43 / (1 + math.Exp(18*(x-0.82)))\n\tp := lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefProbability(key edgeKey, amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok || b.conf == 0 {\n\t\treturn prior\n\t}\n\n\tvar learned float64\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tlearned = 0.995\n\n\tcase b.upperFail > 0 && amt >= b.upperFail:\n\t\tlearned = 0.012\n\n\tdefault:\n\t\testimate := b.estimate\n\t\tif estimate <= 0 {\n\t\t\testimate = capacity / 2\n\t\t}\n\t\tscale := float64(capacity) * 0.08\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(amt)-float64(estimate))/scale,\n\t\t))\n\t\tif learned < 0.012 {\n\t\t\tlearned = 0.012\n\t\t}\n\t\tif learned > 0.995 {\n\t\t\tlearned = 0.995\n\t\t}\n\t}\n\n\tweight := b.conf / (b.conf + 1.5)\n\tif weight > 0.88 {\n\t\tweight = 0.88\n\t}\n\tp := prior*(1-weight) + learned*weight\n\tif p < 0.012 {\n\t\treturn 0.012\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc recordSuccess(key edgeKey, amt,\n\tcapacity lnwire.MilliSatoshi) {\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tb.capacity = capacity\n\tif amt > b.lowerOK {\n\t\tb.lowerOK = amt\n\t}\n\tif b.upperFail > 0 && amt >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\n\thighEstimate := capacity * 85 / 100\n\tif highEstimate < amt {\n\t\thighEstimate = amt\n\t}\n\tif highEstimate > b.estimate {\n\t\tb.estimate = highEstimate\n\t}\n\tb.conf += 1\n\tif b.conf > 6 {\n\t\tb.conf = 6\n\t}\n\tsharedBeliefs.m[key] = b\n}\n\nfunc recordFailure(key edgeKey, amt,\n\tcapacity lnwire.MilliSatoshi) {\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tb.capacity = capacity\n\tif b.upperFail == 0 || amt < b.upperFail {\n\t\tb.upperFail = amt\n\t}\n\tif b.lowerOK >= amt {\n\t\tb.lowerOK = amt * 3 / 4\n\t}\n\n\tlowEstimate := amt / 4\n\tif b.estimate == 0 || lowEstimate < b.estimate {\n\t\tb.estimate = lowEstimate\n\t}\n\tb.conf += 1.25\n\tif b.conf > 6 {\n\t\tb.conf = 6\n\t}\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\toutAmt lnwire.MilliSatoshi\n\tfees lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\n\tedge *candidateEdge\n\tchild *searchState\n\tlive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc stateContains(s *searchState, node route.Vertex) bool {\n\tfor s != nil {\n\t\tif s.node == node {\n\t\t\treturn true\n\t\t}\n\t\ts = s.child\n\t}\n\treturn false\n}\n\nfunc cloneReservations(src map[edgeKey]lnwire.MilliSatoshi,\n) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFee >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFee\n}\n\nfunc riskPrice(budget lnwire.MilliSatoshi) float64 {\n\tif budget == lnwire.MaxMilliSatoshi {\n\t\treturn 420_000\n\t}\n\tprice := float64(budget) / 3\n\tif price < 5_000 {\n\t\tprice = 5_000\n\t}\n\tif price > 420_000 {\n\t\tprice = 420_000\n\t}\n\treturn price\n}\n\nfunc dominated(labels []*searchState, candidate *searchState) bool {\n\tfor _, old := range labels {\n\t\tif !old.live {\n\t\t\tcontinue\n\t\t}\n\t\tif old.score <= candidate.score &&\n\t\t\told.fees <= candidate.fees &&\n\t\t\told.outAmt <= candidate.outAmt {\n\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc trimLabels(labels []*searchState) []*searchState {\n\tlive := labels[:0]\n\tfor _, label := range labels {\n\t\tif label.live {\n\t\t\tlive = append(live, label)\n\t\t}\n\t}\n\tif len(live) <= maxNodeLabels {\n\t\treturn live\n\t}\n\n\tminFee := 0\n\tminAmt := 0\n\tfor i := 1; i < len(live); i++ {\n\t\tif live[i].fees < live[minFee].fees {\n\t\t\tminFee = i\n\t\t}\n\t\tif live[i].outAmt < live[minAmt].outAmt {\n\t\t\tminAmt = i\n\t\t}\n\t}\n\n\tworst := -1\n\tfor i := range live {\n\t\tif i == minFee || i == minAmt {\n\t\t\tcontinue\n\t\t}\n\t\tif worst < 0 || live[i].score > live[worst].score {\n\t\t\tworst = i\n\t\t}\n\t}\n\tif worst < 0 {\n\t\tworst = len(live) - 1\n\t}\n\tlive[worst].live = false\n\treturn append(live[:worst], live[worst+1:]...)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\tload := amt + reservations[edge.key]\n\tif load > edge.capacity {\n\t\treturn 0\n\t}\n\n\tif edge.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\tif !ok || load > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.995\n\t}\n\n\tif failed, ok := r.failedHere[edge.key]; ok {\n\t\tthreshold := failed * 85 / 100\n\t\tif threshold == 0 {\n\t\t\tthreshold = failed\n\t\t}\n\t\tif load >= threshold {\n\t\t\treturn 0\n\t\t}\n\t}\n\n\tp := beliefProbability(edge.key, load, edge.capacity)\n\tif reservations[edge.key] > 0 {\n\t\tratio := float64(reservations[edge.key]) /\n\t\t\tfloat64(edge.capacity)\n\t\tp *= math.Exp(-0.8 * ratio)\n\t}\n\tif p < 0.003 {\n\t\tp = 0.003\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 || r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tprice := riskPrice(feeBudget)\n\troot := &searchState{\n\t\tnode: r.spec.Target,\n\t\toutAmt: amt,\n\t\tlive: true,\n\t}\n\tlabels := map[route.Vertex][]*searchState{\n\t\tr.spec.Target: {root},\n\t}\n\tpq := &searchQueue{}\n\theap.Push(pq, root)\n\n\tfor pq.Len() != 0 {\n\t\tstate := heap.Pop(pq).(*searchState)\n\t\tif !state.live {\n\t\t\tcontinue\n\t\t}\n\t\tif state.node == r.source {\n\t\t\treturn r.buildRoute(state)\n\t\t}\n\t\tif state.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[state.node] {\n\t\t\tif r.policyBad[edge.key] ||\n\t\t\t\tstateContains(state, edge.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeAmt := state.outAmt\n\t\t\tnodeFee := lnwire.MilliSatoshi(0)\n\t\t\tif state.node != r.spec.Target {\n\t\t\t\tnodeFee = forwardingFee(\n\t\t\t\t\tedge, state.edge, state.outAmt,\n\t\t\t\t)\n\t\t\t\tedgeAmt += nodeFee\n\t\t\t}\n\n\t\t\tif !edge.usable(edgeAmt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfees := state.fees + nodeFee\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi &&\n\t\t\t\tfees > feeBudget {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, edgeAmt, reservations)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treusePenalty := 0.0\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treusePenalty = price * 0.70\n\t\t\t}\n\t\t\tcandidate := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\toutAmt: edgeAmt,\n\t\t\t\tfees: fees,\n\t\t\t\tscore: state.score + float64(nodeFee) +\n\t\t\t\t\tprice*(-math.Log(p)) +\n\t\t\t\t\treusePenalty + 1_200,\n\t\t\t\thops: state.hops + 1,\n\t\t\t\tedge: edge,\n\t\t\t\tchild: state,\n\t\t\t\tlive: true,\n\t\t\t}\n\n\t\t\tnodeLabels := labels[candidate.node]\n\t\t\tif dominated(nodeLabels, candidate) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, old := range nodeLabels {\n\t\t\t\tif candidate.score <= old.score &&\n\t\t\t\t\tcandidate.fees <= old.fees &&\n\t\t\t\t\tcandidate.outAmt <= old.outAmt {\n\n\t\t\t\t\told.live = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tnodeLabels = append(nodeLabels, candidate)\n\t\t\tnodeLabels = trimLabels(nodeLabels)\n\t\t\tlabels[candidate.node] = nodeLabels\n\t\t\tif candidate.live {\n\t\t\t\theap.Push(pq, candidate)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(\n\tsourceState *searchState) (*route.Route, error) {\n\n\tvar edges []*candidateEdge\n\tvar amounts []lnwire.MilliSatoshi\n\tstate := sourceState\n\tfor state != nil && state.edge != nil {\n\t\tedges = append(edges, state.edge)\n\t\tamounts = append(amounts, state.outAmt)\n\t\tstate = state.child\n\t}\n\tif len(edges) == 0 || state == nil ||\n\t\tstate.node != r.spec.Target {\n\n\t\treturn nil, errors.New(\"broken route\")\n\t}\n\n\texpiries := make([]uint32, len(edges))\n\texpiries[len(edges)-1] = finalCltvDelta\n\tfor i := len(edges) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(edges[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(edges))\n\tfor i, edge := range edges {\n\t\tforward := amounts[len(amounts)-1]\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(edges) {\n\t\t\tforward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc routeDelivered(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 routeChannelAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeEdges(\n\trt *route.Route) []*candidateEdge {\n\n\tedges := make([]*candidateEdge, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\tfor _, hop := range rt.Hops {\n\t\tedge := r.edgeByRef[edgeRef{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}]\n\t\tedges = append(edges, edge)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\treturn edges\n}\n\nfunc (r *candidateRouter) addRouteReservations(\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i, edge := range r.routeEdges(rt) {\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\t\treservations[edge.key] += routeChannelAmount(rt, i)\n\t}\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tdelivered := routeDelivered(rt)\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) findLargestRoute(\n\tlimit lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tbudget lnwire.MilliSatoshi) (*route.Route,\n\tlnwire.MilliSatoshi, error) {\n\n\tif rt, err := r.findRoute(limit, reservations, budget); err == nil {\n\t\treturn rt, limit, nil\n\t}\n\n\thigh := limit\n\tprobe := limit * 3 / 4\n\tvar best *route.Route\n\tvar low lnwire.MilliSatoshi\n\n\tfor i := 0; i < 26 && probe >= 1_000; i++ {\n\t\trt, err := r.findRoute(probe, reservations, budget)\n\t\tif err == nil {\n\t\t\tbest = rt\n\t\t\tlow = probe\n\t\t\tbreak\n\t\t}\n\t\thigh = probe\n\t\tnext := probe * 3 / 4\n\t\tif next == probe {\n\t\t\tnext--\n\t\t}\n\t\tprobe = next\n\t}\n\tif best == nil {\n\t\treturn nil, 0, errors.New(\"no shard route found\")\n\t}\n\n\tfor i := 0; i < 14 && high > low+1; i++ {\n\t\tmid := low + (high-low)/2\n\t\trt, err := r.findRoute(mid, reservations, budget)\n\t\tif err == nil {\n\t\t\tlow = mid\n\t\t\tbest = rt\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\treturn best, low, nil\n}\n\nfunc (r *candidateRouter) greedyPlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]plannedRoute, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tbudget := r.remainingBudget()\n\tremaining := total\n\tplans := make([]plannedRoute, 0, slots)\n\n\tfor uint32(len(plans)) < slots && remaining > 0 {\n\t\tprior := cloneReservations(reservations)\n\t\trt, shard, err := r.findLargestRoute(\n\t\t\tremaining, reservations, budget,\n\t\t)\n\t\tif err != nil || shard <= 0 {\n\t\t\treturn plans, false\n\t\t}\n\n\t\tplans = append(plans, plannedRoute{\n\t\t\trt: rt,\n\t\t\tprior: prior,\n\t\t})\n\t\tr.addRouteReservations(reservations, rt)\n\n\t\tfee := routeFee(rt)\n\t\tif budget != lnwire.MaxMilliSatoshi {\n\t\t\tif fee > budget {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tbudget -= fee\n\t\t}\n\t\tremaining -= shard\n\t}\n\n\treturn plans, remaining == 0\n}\n\nfunc (r *candidateRouter) equalPlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]plannedRoute, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tbudget := r.remainingBudget()\n\tremaining := total\n\tplans := make([]plannedRoute, 0, parts)\n\n\tfor i := uint32(0); i < parts; i++ {\n\t\tleft := parts - i\n\t\tshard := remaining / lnwire.MilliSatoshi(left)\n\t\tif remaining%lnwire.MilliSatoshi(left) != 0 {\n\t\t\tshard++\n\t\t}\n\t\tif shard <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tprior := cloneReservations(reservations)\n\t\trt, err := r.findRoute(shard, reservations, budget)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tplans = append(plans, plannedRoute{\n\t\t\trt: rt,\n\t\t\tprior: prior,\n\t\t})\n\t\tr.addRouteReservations(reservations, rt)\n\n\t\tfee := routeFee(rt)\n\t\tif budget != lnwire.MaxMilliSatoshi {\n\t\t\tif fee > budget {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tbudget -= fee\n\t\t}\n\t\tremaining -= shard\n\t}\n\n\treturn plans, remaining == 0\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) error {\n\n\tif slots == 0 {\n\t\treturn errors.New(\"maximum parts reached\")\n\t}\n\n\tif plans, ok := r.greedyPlan(total, slots); ok {\n\t\tr.plans = plans\n\t\treturn nil\n\t}\n\n\tfor parts := uint32(2); parts <= slots; parts++ {\n\t\tif plans, ok := r.equalPlan(total, parts); ok {\n\t\t\tr.plans = plans\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn errors.New(\"no route set found\")\n}\n\nfunc (r *candidateRouter) RequestRoute(\n\tamt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tfor len(r.plans) > 0 {\n\t\tplan := r.plans[0]\n\t\tr.plans = r.plans[1:]\n\t\tif routeDelivered(plan.rt) > amt {\n\t\t\tr.plans = nil\n\t\t\tbreak\n\t\t}\n\t\tr.issued[plan.rt] = plan.prior\n\t\treturn plan.rt, nil\n\t}\n\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\tslots := r.spec.MaxParts - inFlightHtlcs\n\tif err := r.makePlan(amt, slots); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(r.plans) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\tplan := r.plans[0]\n\tr.plans = r.plans[1:]\n\tr.issued[plan.rt] = plan.prior\n\treturn plan.rt, nil\n}\n\nfunc 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) observationLoad(\n\trt *route.Route, index int, edge *candidateEdge,\n\tprior map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tload := routeChannelAmount(rt, index)\n\tif prior != nil {\n\t\tload += prior[edge.key]\n\t}\n\treturn load\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tprior := r.issued[rt]\n\tdelete(r.issued, rt)\n\tedges := r.routeEdges(rt)\n\n\tif result.Failure == nil {\n\t\tif r.accounted[rt] {\n\t\t\treturn nil\n\t\t}\n\t\tr.accounted[rt] = true\n\n\t\tfor i, edge := range edges {\n\t\t\tif edge == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tload := r.observationLoad(rt, i, edge, prior)\n\t\t\trecordSuccess(edge.key, load, edge.capacity)\n\t\t\tif failed, ok := r.failedHere[edge.key];\n\t\t\t\tok && load >= failed {\n\n\t\t\t\tdelete(r.failedHere, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.addRouteReservations(r.reserved, rt)\n\t\tr.committedFee += routeFee(rt)\n\t\treturn nil\n\t}\n\n\tr.plans = nil\n\tfailIdx := failureIndex(rt, result.FailureSource)\n\n\tfor i := 0; i < failIdx && i < len(edges); i++ {\n\t\tedge := edges[i]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\t\tload := r.observationLoad(rt, i, edge, prior)\n\t\trecordSuccess(edge.key, load, edge.capacity)\n\t}\n\n\tliquidityFailure := false\n\tpolicyFailure := false\n\tswitch result.Failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\tliquidityFailure = true\n\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\tpolicyFailure = true\n\t}\n\n\tif failIdx >= 0 && failIdx < len(edges) {\n\t\tedge := edges[failIdx]\n\t\tif edge != nil {\n\t\t\tif liquidityFailure {\n\t\t\t\tload := r.observationLoad(\n\t\t\t\t\trt, failIdx, edge, prior,\n\t\t\t\t)\n\t\t\t\told, ok := r.failedHere[edge.key]\n\t\t\t\tif !ok || load < old {\n\t\t\t\t\tr.failedHere[edge.key] = load\n\t\t\t\t}\n\t\t\t\trecordFailure(edge.key, load, edge.capacity)\n\t\t\t} else if policyFailure {\n\t\t\t\tr.policyBad[edge.key] = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 10,
"parent": 1,
"score": 0.1405,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\thopCostMsat = float64(2_500)\n\treusedEdgeCostMsat = float64(700_000)\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 10\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype beliefKey struct {\n\tnetwork string\n\tedge edgeKey\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[beliefKey]liquidityBelief\n}{\n\tm: make(map[beliefKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tnetworkID string\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\theldReservations map[edgeKey]lnwire.MilliSatoshi\n\tpendingReservations map[edgeKey]lnwire.MilliSatoshi\n\tlocalUpperBad map[edgeKey]lnwire.MilliSatoshi\n\tblockedEdges map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tpendingFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\tsuccessParts 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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tnetworkID: fmt.Sprintf(\"%T:%p\", view, view),\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\theldReservations: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpendingReservations: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpperBad: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tblockedEdges: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key beliefKey, amt,\n\tcapacity lnwire.MilliSatoshi, now time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tposition := float64(b.upperBad-amt) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\t\tevidence = 0.012 + 0.983*position\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key beliefKey, amt lnwire.MilliSatoshi,\n\tsuccess bool, now time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int { return len(q) }\nfunc (q searchQueue) Less(i, j int) bool { return q[i].score < q[j].score }\nfunc (q searchQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor current := s; current != nil; current = current.child {\n\t\tif current.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFee := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFee].fee {\n\t\t\t\tminFee = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amount := range src {\n\t\tdst[key] = amount\n\t}\n\treturn dst\n}\n\nfunc addAmount(dst map[edgeKey]lnwire.MilliSatoshi,\n\tkey edgeKey, amount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\tdst[key] += amount\n}\n\nfunc subtractAmount(dst map[edgeKey]lnwire.MilliSatoshi,\n\tkey edgeKey, amount lnwire.MilliSatoshi) {\n\n\tcurrent := dst[key]\n\tif amount >= current {\n\t\tdelete(dst, key)\n\t\treturn\n\t}\n\tdst[key] = current - amount\n}\n\nfunc (r *candidateRouter) reservationSnapshot(\n) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := cloneReservations(r.heldReservations)\n\tfor key, amount := range r.pendingReservations {\n\t\tresult[key] += amount\n\t}\n\treturn result\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\taddAmount(dst, edge.key, meta.amounts[i])\n\t}\n}\n\nfunc subtractReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tsubtractAmount(dst, edge.key, meta.amounts[i])\n\t}\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.pendingFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc (r *candidateRouter) beliefKey(edge edgeKey) beliefKey {\n\treturn beliefKey{\n\t\tnetwork: r.networkID,\n\t\tedge: edge,\n\t}\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcurrent := heap.Pop(pq).(*searchState)\n\t\tif !current.active {\n\t\t\tcontinue\n\t\t}\n\t\tif current.node == r.source {\n\t\t\tresult = current\n\t\t\tbreak\n\t\t}\n\t\tif current.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[current.node] {\n\t\t\tif r.blockedEdges[edge.key] ||\n\t\t\t\tcontainsNode(current, edge.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := current.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif current.node != r.spec.Target {\n\t\t\t\tif current.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(\n\t\t\t\t\tedge, current.nextEdge, current.amount,\n\t\t\t\t)\n\t\t\t\tif nodeCharge >\n\t\t\t\t\tlnwire.MaxMilliSatoshi-current.amount {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif failedAt := r.localUpperBad[edge.key]; failedAt > 0 &&\n\t\t\t\ttotalLoad >= failedAt {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := current.fee + nodeCharge\n\t\t\tif totalFee < current.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tr.beliefKey(edge.key), totalLoad,\n\t\t\t\tedge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.985\n\t\t\t}\n\n\t\t\tscore := current.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuseCost := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuseCost *= 0.45\n\t\t\t\t}\n\t\t\t\tscore += reuseCost\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: current.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: current,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor current := result; current.node != r.spec.Target;\n\t\tcurrent = current.child {\n\n\t\tif current.nextEdge == nil || current.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, current.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tcount := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, count)\n\texpiries := make([]uint32, count)\n\tamounts[count-1] = amt\n\texpiries[count-1] = finalCltvDelta\n\n\tfor i := count - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, count)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < count-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar weightSum int64\n\tfor _, weight := range weights {\n\t\tweightSum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\twhole := int64(total) / weightSum\n\tremainder := int64(total) % weightSum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\twhole*weight + remainder*weight/weightSum,\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - 1\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationKey(allocation []lnwire.MilliSatoshi) string {\n\treturn fmt.Sprint(allocation)\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tseen := make(map[string]bool)\n\tpatterns := make([][]lnwire.MilliSatoshi, 0)\n\n\tadd := func(weights []int64) {\n\t\tallocation := weightedAllocation(total, weights)\n\t\tfor _, amount := range allocation {\n\t\t\tif amount <= 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tkey := allocationKey(allocation)\n\t\tif seen[key] {\n\t\t\treturn\n\t\t}\n\t\tseen[key] = true\n\t\tpatterns = append(patterns, allocation)\n\t}\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\tadd(equal)\n\n\tif parts == 1 {\n\t\treturn patterns\n\t}\n\n\tfor _, heavy := range []int64{35, 45, 55, 65, 75, 82} {\n\t\tlightTotal := int64(100) - heavy\n\t\tlight := lightTotal / int64(parts-1)\n\t\tif light <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor position := 0; position < parts; position++ {\n\t\t\tweights := make([]int64, parts)\n\t\t\tfor i := range weights {\n\t\t\t\tweights[i] = light\n\t\t\t}\n\t\t\tweights[position] = heavy\n\t\t\tadd(weights)\n\t\t}\n\t}\n\n\tascending := make([]int64, parts)\n\tdescending := make([]int64, parts)\n\tfor i := 0; i < parts; i++ {\n\t\tascending[i] = int64(i + 1)\n\t\tdescending[i] = int64(parts - i)\n\t}\n\tadd(ascending)\n\tadd(descending)\n\n\tfor rotation := 1; rotation < parts; rotation++ {\n\t\tweights := make([]int64, parts)\n\t\tfor i := range weights {\n\t\t\tweights[i] = ascending[(i+rotation)%parts]\n\t\t}\n\t\tadd(weights)\n\t}\n\n\treturn patterns\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := r.reservationSnapshot()\n\tremainingBudget := feeBudget\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, remainingBudget)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t}\n\n\ttotalScore += float64(len(plan)-1) * 45_000\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tmaxParts := int(slots)\n\tif total < lnwire.MilliSatoshi(maxParts) {\n\t\tmaxParts = int(total)\n\t}\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\tif slots > 1 {\n\t\tratios := [][2]int64{\n\t\t\t{3, 4},\n\t\t\t{2, 3},\n\t\t\t{3, 5},\n\t\t\t{1, 2},\n\t\t}\n\n\t\tminUseful := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\t\treservations := r.reservationSnapshot()\n\n\t\tfor _, ratio := range ratios {\n\t\t\tshard := total * lnwire.MilliSatoshi(ratio[0]) /\n\t\t\t\tlnwire.MilliSatoshi(ratio[1])\n\t\t\tif shard < minUseful || shard >= total {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfeeCap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tfeeCap = feeBudget * shard / total\n\t\t\t\textra := feeBudget / 10\n\t\t\t\tif feeCap <= lnwire.MaxMilliSatoshi-extra {\n\t\t\t\t\tfeeCap += extra\n\t\t\t\t}\n\t\t\t\tif feeCap > feeBudget {\n\t\t\t\t\tfeeCap = feeBudget\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(shard, reservations, feeCap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\t\t}\n\n\t\tshard := minUseful\n\t\tfor shard >= 10_000 {\n\t\t\tfeeCap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tfeeCap = feeBudget * shard / total\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(shard, reservations, feeCap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\t\t\tshard /= 2\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, planned := range plan {\n\t\ttotal += planned.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tused := r.successParts + uint32(len(r.issued))\n\tif inFlightHtlcs > used {\n\t\tused = inFlightHtlcs\n\t}\n\tif used >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - used\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\taddReservationSet(r.pendingReservations, next.meta)\n\tr.pendingFees += next.meta.fee\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) observationLoads(\n\tmeta *routeMeta) []lnwire.MilliSatoshi {\n\n\treservations := r.reservationSnapshot()\n\tloads := make([]lnwire.MilliSatoshi, len(meta.path))\n\n\tfor i, edge := range meta.path {\n\t\tload := meta.amounts[i] + reservations[edge.key]\n\t\tif load < meta.amounts[i] {\n\t\t\tload = lnwire.MaxMilliSatoshi\n\t\t}\n\t\tloads[i] = load\n\t}\n\n\treturn loads\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\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tif issuedMeta, ok := r.issued[rt]; ok {\n\t\tsubtractReservationSet(r.pendingReservations, issuedMeta)\n\t\tif issuedMeta.fee >= r.pendingFees {\n\t\t\tr.pendingFees = 0\n\t\t} else {\n\t\t\tr.pendingFees -= issuedMeta.fee\n\t\t}\n\t}\n\tdelete(r.issued, rt)\n\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tloads := r.observationLoads(meta)\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(\n\t\t\t\tr.beliefKey(edge.key), loads[i], true, now,\n\t\t\t)\n\t\t\taddAmount(\n\t\t\t\tr.heldReservations, edge.key, meta.amounts[i],\n\t\t\t)\n\t\t\tr.edgePenalty[edge.key] *= 0.3\n\n\t\t\tif failedAt := r.localUpperBad[edge.key];\n\t\t\t\tfailedAt > 0 && loads[i] >= failedAt {\n\n\t\t\t\tdelete(r.localUpperBad, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.successParts++\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tfailIdx := r.failureIndex(rt, result.FailureSource)\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(\n\t\t\tr.beliefKey(meta.path[i].key),\n\t\t\tloads[i], true, now,\n\t\t)\n\t}\n\n\tcode := result.Failure.Code()\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(\n\t\t\t\tr.beliefKey(edge.key),\n\t\t\t\tloads[failIdx], false, now,\n\t\t\t)\n\n\t\t\tcurrent := r.localUpperBad[edge.key]\n\t\t\tif current == 0 || loads[failIdx] < current {\n\t\t\t\tr.localUpperBad[edge.key] = loads[failIdx]\n\t\t\t}\n\t\t\tr.edgePenalty[edge.key] += 450_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.blockedEdges[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\n\t\tdefault:\n\t\t\tr.blockedEdges[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 3_000_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\tguard := meta.fee / 8\n\t\tif guard < 1_000 {\n\t\t\tguard = 1_000\n\t\t}\n\t\tif guard > r.remainingFeeBudget() {\n\t\t\tguard = r.remainingFeeBudget()\n\t\t}\n\t\tr.budgetGuard += guard\n\t}\n\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 500_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 11,
"parent": 0,
"score": 0.3981,
"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\triskPriceMsat = 420000.0\n\thopPriceMsat = 12000.0\n\treusePriceMsat = 650000.0\n\tpartPriceMsat = 25000.0\n\tlargeShardMsat = lnwire.MilliSatoshi(750000000)\n\tbeliefLifetime = int64(3600)\n\tbeliefHalfLife = 900.0\n\tprobabilityFloor = 0.012\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) outboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tfee := int64(e.baseFeeMsat) +\n\t\tint64(amt)*int64(e.feeRatePPM)/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) usableHTLC(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\n// forwardingFee returns the total fee charged by the forwarding node.\n// out is that node's outgoing channel and in is its incoming channel.\nfunc forwardingFee(out, in *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := int64(out.outboundFee(amt))\n\tinboundBase := int64(amt) + outFee\n\tinFee := int64(in.inboundBase) +\n\t\tinboundBase*int64(in.inboundRate)/1_000_000\n\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\tupdated int64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype routeMeta struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdeliver lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\tstatus uint8\n}\n\nconst (\n\tmetaPlanned uint8 = iota\n\tmetaPending\n\tmetaHeld\n\tmetaReleased\n)\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[edgeKey]liquidityBelief\n\tedgeFails map[edgeKey]uint32\n\tpolicyBad map[edgeKey]bool\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\trecords map[*route.Route]*routeMeta\n\theld []*routeMeta\n\tplan []*routeMeta\n\n\tcommittedFee 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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\tedgeFails: make(map[edgeKey]uint32),\n\t\tpolicyBad: make(map[edgeKey]bool),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\trecords: make(map[*route.Route]*routeMeta),\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,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnow := r.now()\n\tsharedBeliefs.Lock()\n\tfor key := range r.edges {\n\t\tbelief, ok := sharedBeliefs.m[key]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tage := (now - belief.updated) / 1_000_000_000\n\t\tif age >= 0 && age <= beliefLifetime {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) now() int64 {\n\treturn r.view.Now().UnixNano()\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\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1.0 / (1.0 + math.Exp((x-0.88)/0.045))\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif load > edge.capacity {\n\t\treturn 0.005\n\t}\n\n\tif edge.from == r.source {\n\t\tif load > r.localBalances[edge.chanID] {\n\t\t\treturn 0.005\n\t\t}\n\t\treturn 0.985\n\t}\n\n\tprior := bimodalPrior(load, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tage := float64(r.now()-belief.updated) / 1e9\n\tif age < 0 || age > float64(beliefLifetime) {\n\t\treturn prior\n\t}\n\n\tconfidence := math.Exp(-math.Ln2 * age / beliefHalfLife)\n\tevidence := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && load <= belief.lowerOK:\n\t\tevidence = 0.995\n\n\tcase belief.upperFail > 0 && load >= belief.upperFail:\n\t\tevidence = probabilityFloor\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(load-belief.lowerOK) / width\n\t\tevidence = 0.995 -\n\t\t\tposition*(0.995-probabilityFloor)\n\n\tcase belief.upperFail > 0:\n\t\tdistance := float64(belief.upperFail-load) /\n\t\t\tmath.Max(1, float64(belief.upperFail))\n\t\tevidence = probabilityFloor +\n\t\t\t(0.90-probabilityFloor)*math.Min(1, 4*distance)\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(load-belief.lowerOK) /\n\t\t\tmath.Max(1, float64(edge.capacity-belief.lowerOK))\n\t\tevidence = 0.995 - 0.55*math.Max(0, distance)\n\t}\n\n\tp := confidence*evidence + (1-confidence)*prior\n\tif p < probabilityFloor {\n\t\tp = probabilityFloor\n\t}\n\tif p > 0.995 {\n\t\tp = 0.995\n\t}\n\n\treturn p\n}\n\nfunc (r *candidateRouter) updateBelief(key edgeKey,\n\tload lnwire.MilliSatoshi, success bool) {\n\n\tbelief := r.beliefs[key]\n\tif success {\n\t\tif load > belief.lowerOK {\n\t\t\tbelief.lowerOK = load\n\t\t}\n\t\tif belief.upperFail != 0 &&\n\t\t\tbelief.lowerOK >= belief.upperFail {\n\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t} else {\n\t\tif belief.upperFail == 0 || load < belief.upperFail {\n\t\t\tbelief.upperFail = load\n\t\t}\n\t\tif belief.lowerOK >= belief.upperFail {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\t}\n\n\tbelief.updated = r.now()\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tout *candidateEdge\n\tforwardAmt lnwire.MilliSatoshi\n\tcost float64\n\trisk float64\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].cost < q[j].cost\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *searchQueue) Push(value any) {\n\titem := value.(*searchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc prependEdge(edge *candidateEdge,\n\tpath []*candidateEdge) []*candidateEdge {\n\n\tresult := make([]*candidateEdge, len(path)+1)\n\tresult[0] = edge\n\tcopy(result[1:], path)\n\treturn result\n}\n\nfunc cloneReservations(\n\tsource map[edgeKey]lnwire.MilliSatoshi,\n) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[edgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi, limited bool) (*routeMeta, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid shard amount\")\n\t}\n\n\tif r.source == r.spec.Target {\n\t\trt := &route.Route{\n\t\t\tTotalTimeLock: finalCltvDelta,\n\t\t\tTotalAmount: deliver,\n\t\t\tSourcePubKey: r.source,\n\t\t}\n\t\treturn &routeMeta{\n\t\t\trt: rt,\n\t\t\tdeliver: deliver,\n\t\t}, nil\n\t}\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tpath: nil,\n\t})\n\n\tbest := make(map[*candidateEdge]float64)\n\tvar winning *searchItem\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\n\t\tif winning != nil && item.cost >= winning.cost {\n\t\t\tbreak\n\t\t}\n\n\t\tif item.out != nil {\n\t\t\tif known, ok := best[item.out]; ok &&\n\t\t\t\titem.cost > known {\n\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.policyBad[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOver := deliver\n\t\t\tincrementalFee := lnwire.MilliSatoshi(0)\n\t\t\tif item.out != nil {\n\t\t\t\tincrementalFee = forwardingFee(\n\t\t\t\t\titem.out, edge, item.forwardAmt,\n\t\t\t\t)\n\t\t\t\tamountOver = item.forwardAmt + incrementalFee\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := amountOver + reserved[edge.key]\n\t\t\tif load > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source &&\n\t\t\t\tload > r.localBalances[edge.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfeesSoFar := amountOver - deliver\n\t\t\tif limited && feesSoFar > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, load)\n\t\t\tedgeRisk := -math.Log(probability)\n\t\t\tcost := item.cost + float64(incrementalFee) +\n\t\t\t\triskPriceMsat*edgeRisk + hopPriceMsat\n\n\t\t\tif reserved[edge.key] > 0 {\n\t\t\t\tcost += reusePriceMsat\n\t\t\t}\n\t\t\tif failures := r.edgeFails[edge.key]; failures != 0 {\n\t\t\t\tcost += float64(failures) * 350000\n\t\t\t}\n\n\t\t\tpath := prependEdge(edge, item.path)\n\t\t\tnext := &searchItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tout: edge,\n\t\t\t\tforwardAmt: amountOver,\n\t\t\t\tcost: cost,\n\t\t\t\trisk: item.risk + edgeRisk,\n\t\t\t\tpath: path,\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tif winning == nil || next.cost < winning.cost {\n\t\t\t\t\twinning = next\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif old, ok := best[edge]; ok && old <= cost {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbest[edge] = cost\n\t\t\theap.Push(pq, next)\n\t\t}\n\t}\n\n\tif winning == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, amounts, err := r.buildRoute(deliver, winning.path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfee := rt.TotalAmount - deliver\n\tif limited && fee > maxFee {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(amounts))\n\tfor i, edge := range winning.path {\n\t\tloads[i] = amounts[i] + reserved[edge.key]\n\t}\n\n\treturn &routeMeta{\n\t\trt: rt,\n\t\tpath: winning.path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdeliver: deliver,\n\t\tfee: fee,\n\t\tscore: winning.cost,\n\t\tstatus: metaPlanned,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, 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] = deliver\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tfee := forwardingFee(path[i+1], path[i], amounts[i+1])\n\t\tamounts[i] = amounts[i+1] + fee\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := deliver\n\t\toutgoingExpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\trt := &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}\n\n\treturn rt, amounts, nil\n}\n\nfunc shardAmounts(total lnwire.MilliSatoshi, parts int,\n\tunequal bool) []lnwire.MilliSatoshi {\n\n\tresult := make([]lnwire.MilliSatoshi, parts)\n\tif parts == 1 {\n\t\tresult[0] = total\n\t\treturn result\n\t}\n\n\tremaining := total\n\tif !unequal {\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tleft := parts - i\n\t\t\tamount := remaining / lnwire.MilliSatoshi(left)\n\t\t\tif remaining%lnwire.MilliSatoshi(left) != 0 {\n\t\t\t\tamount++\n\t\t\t}\n\t\t\tresult[i] = amount\n\t\t\tremaining -= amount\n\t\t}\n\t\treturn result\n\t}\n\n\ttotalWeight := int64(2 * parts * parts)\n\tusedWeight := int64(0)\n\tfor i := 0; i < parts; i++ {\n\t\tif i == parts-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tweight := int64(3*parts - 1 - 2*i)\n\t\tamount := lnwire.MilliSatoshi(\n\t\t\tfloat64(total) * float64(weight) /\n\t\t\t\tfloat64(totalWeight),\n\t\t)\n\t\tif amount <= 0 {\n\t\t\tamount = 1\n\t\t}\n\t\tresult[i] = amount\n\t\tremaining -= amount\n\t\tusedWeight += weight\n\t\t_ = usedWeight\n\t}\n\n\treturn result\n}\n\nfunc (r *candidateRouter) remainingBudget() (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn 0, false\n\t}\n\tif r.committedFee >= r.spec.FeeLimitMsat {\n\t\treturn 0, true\n\t}\n\n\treturn r.spec.FeeLimitMsat - r.committedFee, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tpartsLeft uint32) ([]*routeMeta, error) {\n\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tremainingBudget, limited := r.remainingBudget()\n\tmaxParts := int(partsLeft)\n\tif maxParts < 1 {\n\t\tmaxParts = 1\n\t}\n\n\tpreferred := int(\n\t\t(total + largeShardMsat - 1) / largeShardMsat,\n\t)\n\tif preferred < 1 {\n\t\tpreferred = 1\n\t}\n\tif preferred > maxParts {\n\t\tpreferred = maxParts\n\t}\n\n\tvar bestPlan []*routeMeta\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tshapeCount := 1\n\t\tif parts > 1 {\n\t\t\tshapeCount = 2\n\t\t}\n\n\t\tfor shape := 0; shape < shapeCount; shape++ {\n\t\t\tamounts := shardAmounts(total, parts, shape == 1)\n\t\t\ttempReservations := cloneReservations(r.reserved)\n\t\t\tplan := make([]*routeMeta, 0, parts)\n\t\t\tplanFee := lnwire.MilliSatoshi(0)\n\t\t\tplanScore := float64(parts) * partPriceMsat\n\t\t\tremainingAmount := total\n\t\t\tvalid := true\n\n\t\t\tfor _, shard := range amounts {\n\t\t\t\tif shard <= 0 || shard > remainingAmount {\n\t\t\t\t\tvalid = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tmaxShardFee := lnwire.MilliSatoshi(0)\n\t\t\t\tif limited {\n\t\t\t\t\tif planFee >= remainingBudget {\n\t\t\t\t\t\tmaxShardFee = 0\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbudgetLeft := remainingBudget - planFee\n\t\t\t\t\t\tmaxShardFee = lnwire.MilliSatoshi(\n\t\t\t\t\t\t\tfloat64(budgetLeft) *\n\t\t\t\t\t\t\t\tfloat64(shard) /\n\t\t\t\t\t\t\t\tfloat64(remainingAmount),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tmeta, err := r.findRoute(\n\t\t\t\t\tshard, tempReservations,\n\t\t\t\t\tmaxShardFee, limited,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\tvalid = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tplan = append(plan, meta)\n\t\t\t\tplanFee += meta.fee\n\t\t\t\tplanScore += meta.score\n\t\t\t\tremainingAmount -= shard\n\n\t\t\t\tfor i, edge := range meta.path {\n\t\t\t\t\ttempReservations[edge.key] +=\n\t\t\t\t\t\tmeta.amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !valid || remainingAmount != 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif limited && planFee > remainingBudget {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif parts < preferred {\n\t\t\t\tplanScore += float64(preferred-parts) * 900000\n\t\t\t}\n\n\t\t\tif planScore < bestScore {\n\t\t\t\tbestScore = planScore\n\t\t\t\tbestPlan = plan\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(bestPlan) == 0 {\n\t\treturn nil, errors.New(\"no route set found\")\n\t}\n\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) addReservation(meta *routeMeta) {\n\tfor i, edge := range meta.path {\n\t\tr.reserved[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) releaseReservation(meta *routeMeta) {\n\tif meta == nil || meta.status == metaReleased {\n\t\treturn\n\t}\n\n\tfor i, edge := range meta.path {\n\t\tamount := meta.amounts[i]\n\t\tcurrent := r.reserved[edge.key]\n\t\tif current <= amount {\n\t\t\tdelete(r.reserved, edge.key)\n\t\t} else {\n\t\t\tr.reserved[edge.key] = current - amount\n\t\t}\n\t}\n\tmeta.status = metaReleased\n}\n\nfunc (r *candidateRouter) discardPlan() {\n\tfor _, meta := range r.plan {\n\t\tr.releaseReservation(meta)\n\t}\n\tr.plan = nil\n}\n\nfunc (r *candidateRouter) syncHeld(inFlight uint32) {\n\tfor len(r.held) > int(inFlight) {\n\t\tmeta := r.held[0]\n\t\tr.held = r.held[1:]\n\n\t\tr.releaseReservation(meta)\n\t\tdelete(r.records, meta.rt)\n\n\t\tif len(meta.path) != 0 {\n\t\t\tfirst := meta.path[0]\n\t\t\tspent := meta.amounts[0]\n\t\t\tbalance := r.localBalances[first.chanID]\n\t\t\tif spent >= balance {\n\t\t\t\tr.localBalances[first.chanID] = 0\n\t\t\t} else {\n\t\t\t\tr.localBalances[first.chanID] =\n\t\t\t\t\tbalance - spent\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tr.syncHeld(inFlightHtlcs)\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment complete\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tif len(r.plan) != 0 && r.plan[0].deliver > amt {\n\t\tr.discardPlan()\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\t\tplan, err := r.makePlan(amt, partsLeft)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, meta := range plan {\n\t\t\tr.addReservation(meta)\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tmeta := r.plan[0]\n\tr.plan = r.plan[1:]\n\tmeta.status = metaPending\n\tr.records[meta.rt] = meta\n\n\treturn meta.rt, nil\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(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tmeta := r.records[rt]\n\tif meta == nil {\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tr.updateBelief(edge.key, meta.loads[i], true)\n\t\t}\n\n\t\tr.committedFee += meta.fee\n\t\tmeta.status = metaHeld\n\t\tr.held = append(r.held, meta)\n\t\treturn nil\n\t}\n\n\tr.releaseReservation(meta)\n\tdelete(r.records, rt)\n\tr.discardPlan()\n\n\tfailIdx := r.failureIndex(rt, result.FailureSource)\n\tif failIdx >= 0 {\n\t\tfor i := 0; i < failIdx && i < len(meta.path); i++ {\n\t\t\tr.updateBelief(\n\t\t\t\tmeta.path[i].key, meta.loads[i], true,\n\t\t\t)\n\t\t}\n\t}\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\t\tedge := meta.path[failIdx]\n\t\t\tr.updateBelief(\n\t\t\t\tedge.key, meta.loads[failIdx], false,\n\t\t\t)\n\t\t\tr.edgeFails[edge.key]++\n\t\t} else {\n\t\t\tfor _, edge := range meta.path {\n\t\t\t\tr.edgeFails[edge.key]++\n\t\t\t}\n\t\t}\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\t\tedge := meta.path[failIdx]\n\t\t\tr.policyBad[edge.key] = true\n\t\t} else {\n\t\t\tfor _, edge := range meta.path {\n\t\t\t\tr.edgeFails[edge.key]++\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\t\tr.edgeFails[meta.path[failIdx].key]++\n\t\t} else {\n\t\t\tfor _, edge := range meta.path {\n\t\t\t\tr.edgeFails[edge.key]++\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nvar _ routing.SimRouter = (*candidateRouter)(nil)\n\nfunc brokenPathError(node route.Vertex) error {\n\treturn fmt.Errorf(\"broken path at %v\", node)\n}"
}
},
{
"id": 12,
"parent": 1,
"score": 0.3453,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\thopCostMsat = float64(28_000)\n\treusedEdgeCostMsat = float64(1_800_000)\n\textraPartCostMsat = float64(80_000)\n\tmaxSearchHops = 40\n\tmaxLabelsPerNode = 14\n\tmaxRouterAttempts = uint32(96)\n\tminFallbackShardMsat = lnwire.MilliSatoshi(5_000)\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinboundBasis := int64(amt)\n\tif outbound > 0 && inboundBasis <= math.MaxInt64-outbound {\n\t\tinboundBasis += outbound\n\t}\n\n\tinbound := int64(in.inboundBase) +\n\t\tinboundBasis*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tpathHash uint64\n\tbusy bool\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype amountPenalty struct {\n\tthreshold lnwire.MilliSatoshi\n\tcost float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tliquidityPenalty map[edgeKey]amountPenalty\n\tpolicyPenalty map[edgeKey]float64\n\tpathPenalty map[uint64]float64\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\tsuccessfulParts uint32\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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tliquidityPenalty: make(map[edgeKey]amountPenalty),\n\t\tpolicyPenalty: make(map[edgeKey]float64),\n\t\tpathPenalty: make(map[uint64]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.015\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence = 0.015 + 0.98*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tgain float64, now time.Time) {\n\n\tif amt <= 0 || gain <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+gain)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\nfunc mixPathHash(h, chanID uint64, from route.Vertex) uint64 {\n\th ^= chanID\n\th *= 1099511628211\n\th ^= uint64(from[0])<<8 | uint64(from[1])\n\th *= 1099511628211\n\treturn h\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tpathHash uint64\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) 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 containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tprotected := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[protected].fee {\n\t\t\t\tprotected = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == protected {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\told := dst[edge.key]\n\t\tif meta.amounts[i] > lnwire.MaxMilliSatoshi-old {\n\t\t\tdst[edge.key] = lnwire.MaxMilliSatoshi\n\t\t\tcontinue\n\t\t}\n\t\tdst[edge.key] = old + meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) activeReservations(\n) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := cloneReservations(r.reserved)\n\tfor _, meta := range r.issued {\n\t\taddReservationSet(result, meta)\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tfor _, meta := range r.issued {\n\t\tif meta.fee > lnwire.MaxMilliSatoshi-spent {\n\t\t\treturn 0\n\t\t}\n\t\tspent += meta.fee\n\t}\n\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) * 0.55\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc (r *candidateRouter) liquidityCost(key edgeKey,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tpenalty, ok := r.liquidityPenalty[key]\n\tif !ok || penalty.cost <= 0 {\n\t\treturn 0\n\t}\n\tif penalty.threshold <= 0 || load >= penalty.threshold {\n\t\treturn penalty.cost\n\t}\n\n\tratio := float64(load) / float64(penalty.threshold)\n\treturn penalty.cost * (0.05 + 0.95*ratio*ratio)\n}\n\nfunc (r *candidateRouter) bumpLiquidityPenalty(key edgeKey,\n\tamount lnwire.MilliSatoshi, increment float64) {\n\n\tp := r.liquidityPenalty[key]\n\tif p.threshold == 0 || amount < p.threshold {\n\t\tp.threshold = amount\n\t}\n\tp.cost += increment\n\tif p.cost > 4_000_000 {\n\t\tp.cost = 4_000_000\n\t}\n\tr.liquidityPenalty[key] = p\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi, busy bool) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tpathHash: 1469598103934665603,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\tif reserved > lnwire.MaxMilliSatoshi-over {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.985\n\t\t\t\tif busy {\n\t\t\t\t\tp = 0.97\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.policyPenalty[edge.key] +\n\t\t\t\tr.liquidityCost(edge.key, totalLoad)\n\n\t\t\tif reserved > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.45\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tpathHash := mixPathHash(\n\t\t\t\tcur.pathHash, edge.chanID, edge.from,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tscore += r.pathPenalty[pathHash]\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tpathHash: pathHash,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tif reservations[edge.key] >\n\t\t\tlnwire.MaxMilliSatoshi-amounts[i] {\n\n\t\t\treturn nil, errors.New(\"route load overflow\")\n\t\t}\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t\tpathHash: result.pathHash,\n\t\tbusy: busy,\n\t}\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tbase := int64(total) / sum\n\tremainder := int64(total) % sum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tbase*weight + remainder*weight/sum,\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tascending := make([]int64, parts)\n\tdescending := make([]int64, parts)\n\tgeometricUp := make([]int64, parts)\n\tgeometricDown := make([]int64, parts)\n\tdominantFirst := make([]int64, parts)\n\tdominantLast := make([]int64, parts)\n\n\tfor i := 0; i < parts; i++ {\n\t\tequal[i] = 1\n\t\tascending[i] = int64(i + 1)\n\t\tdescending[i] = int64(parts - i)\n\n\t\tshift := i\n\t\tif shift > 6 {\n\t\t\tshift = 6\n\t\t}\n\t\tgeometricUp[i] = int64(1) << shift\n\n\t\tshift = parts - i - 1\n\t\tif shift > 6 {\n\t\t\tshift = 6\n\t\t}\n\t\tgeometricDown[i] = int64(1) << shift\n\n\t\tdominantFirst[i] = 1\n\t\tdominantLast[i] = 1\n\t}\n\tif parts > 1 {\n\t\tdominantFirst[0] = int64(parts + 2)\n\t\tdominantLast[parts-1] = int64(parts + 2)\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\tif parts > 1 {\n\t\tpatterns = append(\n\t\t\tpatterns,\n\t\t\tweightedAllocation(total, ascending),\n\t\t\tweightedAllocation(total, descending),\n\t\t\tweightedAllocation(total, geometricUp),\n\t\t\tweightedAllocation(total, geometricDown),\n\t\t\tweightedAllocation(total, dominantFirst),\n\t\t\tweightedAllocation(total, dominantLast),\n\t\t)\n\t}\n\n\tif parts == 2 {\n\t\tfor _, weights := range [][]int64{\n\t\t\t{45, 55}, {55, 45},\n\t\t\t{35, 65}, {65, 35},\n\t\t\t{25, 75}, {75, 25},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc proportionalBudget(totalBudget, shard,\n\tremaining lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif totalBudget == lnwire.MaxMilliSatoshi ||\n\t\tshard >= remaining {\n\n\t\treturn totalBudget\n\t}\n\n\tcap := lnwire.MilliSatoshi(\n\t\tfloat64(totalBudget) * float64(shard) /\n\t\t\tfloat64(remaining),\n\t)\n\tif cap < 0 {\n\t\treturn 0\n\t}\n\tif cap > totalBudget {\n\t\treturn totalBudget\n\t}\n\treturn cap\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi,\n\tbaseReservations map[edgeKey]lnwire.MilliSatoshi,\n\tproportional bool, busy bool) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(baseReservations)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 ||\n\t\t\tshard > lnwire.MaxMilliSatoshi-remainingAmount {\n\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tfeeCap := remainingBudget\n\t\tif proportional &&\n\t\t\tremainingBudget != lnwire.MaxMilliSatoshi {\n\n\t\t\tfeeCap = proportionalBudget(\n\t\t\t\tremainingBudget, shard, remainingAmount,\n\t\t\t)\n\t\t}\n\n\t\tpr, err := r.findRoute(\n\t\t\tshard, reservations, feeCap, busy,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32, busy bool) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tbaseReservations := r.activeReservations()\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tif lnwire.MilliSatoshi(parts) > total {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tfor _, proportional := range []bool{true, false} {\n\t\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\t\tallocation, feeBudget,\n\t\t\t\t\tbaseReservations, proportional, busy,\n\t\t\t\t)\n\t\t\t\tif ok && score < bestScore {\n\t\t\t\t\tbest = plan\n\t\t\t\t\tbestScore = score\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\tif slots > 1 {\n\t\tshard := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tfor shard >= minFallbackShardMsat {\n\t\t\tfeeCap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tfeeCap = proportionalBudget(\n\t\t\t\t\tfeeBudget, shard, total,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(\n\t\t\t\tshard, baseReservations, feeCap, busy,\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\n\t\t\tnext := shard * 2 / 3\n\t\t\tif next >= shard {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tshard = next\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt budget exhausted\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tused := r.successfulParts\n\tif inFlightHtlcs > used {\n\t\tused = inFlightHtlcs\n\t}\n\tif used >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - used\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tbusy := inFlightHtlcs > 0 || len(r.issued) > 0\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots, busy)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tnext.meta.busy = busy\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\treservations := r.activeReservations()\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\tpathHash := uint64(1469598103934665603)\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tif reservations[key] >\n\t\t\tlnwire.MaxMilliSatoshi-amount {\n\n\t\t\treturn nil\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+reservations[key])\n\t\tpathHash = mixPathHash(pathHash, edge.chanID, edge.from)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t\tpathHash: pathHash,\n\t\tbusy: len(r.issued) > 0,\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(\n\t\t\t\tedge.key, meta.loads[i], true, 0.5, now,\n\t\t\t)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\n\t\t\tif penalty, ok := r.liquidityPenalty[edge.key]; ok {\n\t\t\t\tpenalty.cost *= 0.25\n\t\t\t\tif penalty.cost < 10_000 {\n\t\t\t\t\tdelete(r.liquidityPenalty, edge.key)\n\t\t\t\t} else {\n\t\t\t\t\tr.liquidityPenalty[edge.key] = penalty\n\t\t\t\t}\n\t\t\t}\n\t\t\tr.policyPenalty[edge.key] *= 0.8\n\t\t}\n\n\t\tr.pathPenalty[meta.pathHash] *= 0.2\n\t\tr.committedFees += meta.fee\n\t\tr.successfulParts++\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(\n\t\t\tmeta.path[i].key, meta.loads[i], true, 0.3, now,\n\t\t)\n\t}\n\n\tcode := result.Failure.Code()\n\tr.pathPenalty[meta.pathHash] += 300_000\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tgain := 0.55\n\t\t\tif meta.busy {\n\t\t\t\tgain = 0.25\n\t\t\t}\n\t\t\tupdateBelief(\n\t\t\t\tedge.key, meta.loads[failIdx],\n\t\t\t\tfalse, gain, now,\n\t\t\t)\n\t\t\tr.bumpLiquidityPenalty(\n\t\t\t\tedge.key, meta.loads[failIdx], 650_000,\n\t\t\t)\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.policyPenalty[edge.key] += 6_000_000\n\n\t\tdefault:\n\t\t\tr.policyPenalty[edge.key] += 1_500_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.pathPenalty[meta.pathHash] += 2_500_000\n\t\tfor i, edge := range meta.path {\n\t\t\tr.bumpLiquidityPenalty(\n\t\t\t\tedge.key, meta.loads[i], 120_000,\n\t\t\t)\n\t\t}\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.pathPenalty[meta.pathHash] += 5_000_000\n\t\tfor _, edge := range meta.path {\n\t\t\tr.policyPenalty[edge.key] += 300_000\n\t\t}\n\n\t\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\t\tguard := meta.fee / 10\n\t\t\tif guard < 1_000 {\n\t\t\t\tguard = 1_000\n\t\t\t}\n\t\t\tif guard <= lnwire.MaxMilliSatoshi-r.budgetGuard {\n\t\t\t\tr.budgetGuard += guard\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\tr.pathPenalty[meta.pathHash] += 1_500_000\n\t\tfor _, edge := range meta.path {\n\t\t\tr.policyPenalty[edge.key] += 100_000\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 13,
"parent": 0,
"score": 0.1969,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCLTV = uint32(40)\n\tcandidateRiskPrice = 420000.0\n\tcandidateHopPrice = 8000.0\n\tcandidateReuseNats = 1.15\n\tcandidateMinShard = lnwire.MilliSatoshi(10000)\n\tcandidateProbFloor = 0.012\n\tcandidateProbCeiling = 0.995\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 int64\n\tfeeRatePPM int64\n\tcltvDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc (e *candidateEdge) outgoingFee(amt lnwire.MilliSatoshi) int64 {\n\tfee := e.baseFeeMsat + int64(amt)*e.feeRatePPM/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn fee\n}\n\n// forwardingFee is the fee charged by the node between incoming and\n// outgoing. The inbound component belongs to the incoming channel and is\n// assessed on the forwarded amount plus the ordinary outbound fee.\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif incoming == nil || outgoing == nil {\n\t\treturn 0\n\t}\n\n\toutFee := outgoing.outgoingFee(amt)\n\tinBasis := int64(amt) + outFee\n\tinFee := incoming.inboundBase +\n\t\tinBasis*incoming.inboundRate/1_000_000\n\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tsuccess uint32\n\tfailure uint32\n}\n\nvar candidateSharedBeliefs = struct {\n\tsync.Mutex\n\tm map[candidateEdgeKey]candidateBelief\n}{\n\tm: make(map[candidateEdgeKey]candidateBelief),\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn candidateProbFloor\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\t// A bimodal survival prior: one component represents channels whose\n\t// directional balance is near zero, the other channels whose balance\n\t// is near full capacity.\n\tlowMode := 0.5 * math.Exp(-x/0.045)\n\thighMode := 0.5 / (1 + math.Exp((x-0.94)/0.035))\n\tp := lowMode + highMode\n\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc candidateProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := candidatePrior(load, e.capacity)\n\n\tcandidateSharedBeliefs.Lock()\n\tbelief, ok := candidateSharedBeliefs.m[e.key]\n\tcandidateSharedBeliefs.Unlock()\n\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn candidateProbCeiling\n\t}\n\n\tif belief.upperBad > 0 && load >= belief.upperBad {\n\t\tp := prior * 0.08\n\t\tif p < candidateProbFloor {\n\t\t\tp = candidateProbFloor\n\t\t}\n\t\treturn p\n\t}\n\n\tvar learned float64\n\tvar confidence float64\n\n\tswitch {\n\tcase belief.lowerOK > 0 && belief.upperBad > belief.lowerOK:\n\t\tmid := 0.5 * float64(belief.lowerOK+belief.upperBad)\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad-belief.lowerOK)*0.18,\n\t\t\tfloat64(e.capacity)*0.012,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp((float64(load)-mid)/width))\n\t\tconfidence = 0.88\n\n\tcase belief.upperBad > 0:\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad)*0.14,\n\t\t\tfloat64(e.capacity)*0.015,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(load)-float64(belief.upperBad))/width,\n\t\t))\n\t\tconfidence = 0.72\n\n\tcase belief.lowerOK > 0:\n\t\tif load <= belief.lowerOK {\n\t\t\tlearned = candidateProbCeiling\n\t\t} else {\n\t\t\texcess := float64(load-belief.lowerOK) /\n\t\t\t\tmath.Max(float64(e.capacity), 1)\n\t\t\tlearned = candidateProbCeiling *\n\t\t\t\tmath.Exp(-excess/0.12)\n\t\t}\n\t\tconfidence = 0.62\n\n\tdefault:\n\t\treturn prior\n\t}\n\n\tp := confidence*learned + (1-confidence)*prior\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > candidateProbCeiling {\n\t\treturn candidateProbCeiling\n\t}\n\treturn p\n}\n\nfunc candidateRecordSuccess(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tb.success++\n\n\t// Contradictory observations can occur when concurrent holds are\n\t// released. Preserve both observations as a soft interval instead of\n\t// permanently retiring the channel.\n\tif b.upperBad > 0 && b.lowerOK >= b.upperBad {\n\t\tb.upperBad = b.lowerOK + 1\n\t}\n\tcandidateSharedBeliefs.m[key] = b\n}\n\nfunc candidateRecordFailure(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif b.upperBad == 0 || load < b.upperBad {\n\t\tb.upperBad = load\n\t}\n\tb.failure++\n\tcandidateSharedBeliefs.m[key] = b\n}\n\ntype candidateSearchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk float64\n\thops int\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype candidateSearchQueue []*candidateSearchItem\n\nfunc (q candidateSearchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateSearchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateSearchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateSearchQueue) Push(x any) {\n\titem := x.(*candidateSearchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateSearchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocal map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route]bool\n\tpenalty map[candidateEdgeKey]float64\n\tbanned map[candidateEdgeKey]bool\n\n\tplan []*route.Route\n\tcommittedFees lnwire.MilliSatoshi\n\tfailures 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[candidateEdgeKey]*candidateEdge),\n\t\tlocal: localBalances,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route]bool),\n\t\tpenalty: make(map[candidateEdgeKey]float64),\n\t\tbanned: make(map[candidateEdgeKey]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\n\t\t\t\t\tbaseFeeMsat: int64(policy.FeeBaseMSat),\n\t\t\t\t\tfeeRatePPM: int64(\n\t\t\t\t\t\tpolicy.FeeProportionalMillionths,\n\t\t\t\t\t),\n\t\t\t\t\tcltvDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\tr.edges[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) budgetLeft() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateAdd(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b > 0 && a > lnwire.MaxMilliSatoshi-b {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn a + b\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid shard amount\")\n\t}\n\n\tdist := make(map[route.Vertex]float64)\n\tdist[r.spec.Target] = 0\n\n\tpq := &candidateSearchQueue{}\n\theap.Push(pq, &candidateSearchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateSearchItem)\n\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.score > best+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item.path, deliver)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\tif r.routeFee(rt) > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tquality := 1.0\n\t\t\tif item.hops > 0 {\n\t\t\t\tquality = math.Exp(-item.risk / float64(item.hops))\n\t\t\t}\n\t\t\treturn rt, quality, nil\n\t\t}\n\n\t\tvar outgoing *candidateEdge\n\t\tif len(item.path) > 0 {\n\t\t\toutgoing = item.path[0]\n\t\t}\n\n\t\tfor _, incoming := range r.incoming[item.node] {\n\t\t\tif r.banned[incoming.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOver := item.amount\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tamountOver = candidateAdd(\n\t\t\t\t\tamountOver,\n\t\t\t\t\tforwardingFee(incoming, outgoing, item.amount),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif !incoming.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi &&\n\t\t\t\tamountOver-deliver > maxFee {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := amountOver\n\t\t\tload = candidateAdd(load, r.reserved[incoming.key])\n\t\t\tif extra != nil {\n\t\t\t\tload = candidateAdd(load, extra[incoming.key])\n\t\t\t}\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar probability float64\n\t\t\tif incoming.key.from == r.source {\n\t\t\t\tbalance, ok := r.local[incoming.key.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprobability = 0.999\n\t\t\t} else {\n\t\t\t\tprobability = candidateProbability(incoming, load)\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability)\n\t\t\tedgePenalty := r.penalty[incoming.key]\n\t\t\treuse := 0.0\n\t\t\tif r.reserved[incoming.key] > 0 ||\n\t\t\t\t(extra != nil && extra[incoming.key] > 0) {\n\n\t\t\t\treuse = candidateReuseNats\n\t\t\t}\n\n\t\t\tfeeIncrement := float64(amountOver - item.amount)\n\t\t\tnewScore := item.score + feeIncrement +\n\t\t\t\tcandidateRiskPrice*(risk+edgePenalty+reuse) +\n\t\t\t\tcandidateHopPrice\n\n\t\t\toldScore, exists := dist[incoming.key.from]\n\t\t\tif exists && oldScore <= newScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = incoming\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tdist[incoming.key.from] = newScore\n\t\t\theap.Push(pq, &candidateSearchItem{\n\t\t\t\tnode: incoming.key.from,\n\t\t\t\tamount: amountOver,\n\t\t\t\tscore: newScore,\n\t\t\t\trisk: item.risk + risk,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tdeliver lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = candidateFinalCLTV\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := forwardingFee(path[i], path[i+1], amounts[i+1])\n\t\tamounts[i] = candidateAdd(amounts[i+1], fee)\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].cltvDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := candidateFinalCLTV\n\t\tif i < n-1 {\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) forEachRouteEdge(rt *route.Route,\n\tfn func(candidateEdgeKey, lnwire.MilliSatoshi, int)) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tfn(key, amount, i)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc (r *candidateRouter) addVirtualReservation(\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\treserved[key] = candidateAdd(reserved[key], amount)\n\t})\n}\n\nfunc (r *candidateRouter) reserveIssued(rt *route.Route) {\n\tif r.issued[rt] {\n\t\treturn\n\t}\n\tr.issued[rt] = true\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tr.reserved[key] = candidateAdd(r.reserved[key], amount)\n\t})\n}\n\nfunc (r *candidateRouter) releaseIssued(rt *route.Route) {\n\tif !r.issued[rt] {\n\t\treturn\n\t}\n\tdelete(r.issued, rt)\n\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tcurrent := r.reserved[key]\n\t\tif current <= amount {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = current - amount\n\t\t}\n\t})\n}\n\nfunc (r *candidateRouter) maxFeeForPlan(\n\talready lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tleft := r.budgetLeft()\n\tif left == lnwire.MaxMilliSatoshi {\n\t\treturn left\n\t}\n\tif already >= left {\n\t\treturn 0\n\t}\n\treturn left - already\n}\n\nfunc (r *candidateRouter) tryPlan(amount lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, bool) {\n\n\tif parts < 1 {\n\t\treturn nil, false\n\t}\n\n\tremaining := amount\n\tvirtual := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tplannedFees := lnwire.MilliSatoshi(0)\n\tplan := make([]*route.Route, 0, parts)\n\n\tfor i := 0; i < parts; i++ {\n\t\tleft := parts - i\n\t\tif remaining < candidateMinShard*lnwire.MilliSatoshi(left) {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tshard := remaining / lnwire.MilliSatoshi(left)\n\n\t\t// Let early corridors take a modestly larger unequal share when\n\t\t// they can carry it, while reserving enough for all later parts.\n\t\tvar candidates []lnwire.MilliSatoshi\n\t\tif left > 1 {\n\t\t\tmaximum := remaining -\n\t\t\t\tcandidateMinShard*lnwire.MilliSatoshi(left-1)\n\t\t\tboosted := shard + shard/4\n\t\t\tif boosted > maximum {\n\t\t\t\tboosted = maximum\n\t\t\t}\n\t\t\tif boosted > shard {\n\t\t\t\tcandidates = append(candidates, boosted)\n\t\t\t}\n\t\t}\n\t\tcandidates = append(candidates, shard)\n\n\t\tvar selected *route.Route\n\t\tfor _, candidate := range candidates {\n\t\t\tmaxFee := r.maxFeeForPlan(plannedFees)\n\t\t\trt, _, err := r.findRoute(candidate, virtual, maxFee)\n\t\t\tif err == nil {\n\t\t\t\tselected = rt\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif selected == nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tfee := r.routeFee(selected)\n\t\tif r.budgetLeft() != lnwire.MaxMilliSatoshi &&\n\t\t\tplannedFees+fee > r.budgetLeft() {\n\n\t\t\treturn nil, false\n\t\t}\n\n\t\tplan = append(plan, selected)\n\t\tplannedFees += fee\n\t\tr.addVirtualReservation(virtual, selected)\n\t\tremaining -= r.routeAmount(selected)\n\t}\n\n\treturn plan, remaining == 0\n}\n\nfunc (r *candidateRouter) makePlan(amount lnwire.MilliSatoshi,\n\tslots uint32) ([]*route.Route, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tmaxFee := r.budgetLeft()\n\tfull, quality, fullErr := r.findRoute(amount, nil, maxFee)\n\n\tif slots == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tpreferred := 1\n\tswitch {\n\tcase fullErr != nil:\n\t\tpreferred = 2\n\tcase quality < 0.43:\n\t\tpreferred = 3\n\tcase quality < 0.62 && amount >= 50_000_000:\n\t\tpreferred = 2\n\t}\n\n\tif r.failures > 0 && preferred < 2 {\n\t\tpreferred = 2\n\t}\n\tif r.failures >= 2 && preferred < 3 {\n\t\tpreferred = 3\n\t}\n\tif preferred > int(slots) {\n\t\tpreferred = int(slots)\n\t}\n\n\tif preferred == 1 && fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tfor parts := preferred; parts <= int(slots); parts++ {\n\t\tplan, ok := r.tryPlan(amount, parts)\n\t\tif ok {\n\t\t\treturn plan, nil\n\t\t}\n\t}\n\n\tif fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\t// Last-resort lower retry. This preserves the useful evidence-driven\n\t// retry behavior without an unbounded halving ladder.\n\tshard := amount / lnwire.MilliSatoshi(slots)\n\tif shard < candidateMinShard {\n\t\tshard = candidateMinShard\n\t}\n\tfor shard >= candidateMinShard {\n\t\trt, _, err := r.findRoute(shard, nil, r.budgetLeft())\n\t\tif err == nil {\n\t\t\treturn []*route.Route{rt}, nil\n\t\t}\n\t\tif shard == candidateMinShard {\n\t\t\tbreak\n\t\t}\n\t\tshard = shard * 2 / 3\n\t\tif shard < candidateMinShard {\n\t\t\tshard = candidateMinShard\n\t\t}\n\t}\n\n\treturn nil, fullErr\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tfor {\n\t\tif len(r.plan) == 0 {\n\t\t\tmaxParts := r.spec.MaxParts\n\t\t\tif maxParts == 0 {\n\t\t\t\tmaxParts = 1\n\t\t\t}\n\t\t\tif inFlightHtlcs >= maxParts {\n\t\t\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t\t\t}\n\n\t\t\tplan, err := r.makePlan(amt, maxParts-inFlightHtlcs)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr.plan = plan\n\t\t}\n\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tif r.routeAmount(rt) > amt {\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tbudget := r.budgetLeft()\n\t\tif budget != lnwire.MaxMilliSatoshi && fee > budget {\n\t\t\tr.plan = nil\n\t\t\tr.failures++\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserveIssued(rt)\n\t\treturn rt, nil\n\t}\n}\n\nfunc (r *candidateRouter) routeFailureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) routeEdgeAt(rt *route.Route,\n\tindex int) (candidateEdgeKey, lnwire.MilliSatoshi, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tamount := rt.TotalAmount\n\tif index > 0 {\n\t\tamount = rt.Hops[index-1].AmtToForward\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, amount, true\n}\n\nfunc (r *candidateRouter) observedLoad(key candidateEdgeKey,\n\tfallback lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif load := r.reserved[key]; load > 0 {\n\t\treturn load\n\t}\n\treturn fallback\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\t\tcandidateRecordSuccess(\n\t\t\t\tkey, r.observedLoad(key, amount),\n\t\t\t)\n\t\t\tif r.penalty[key] > 0.25 {\n\t\t\t\tr.penalty[key] -= 0.25\n\t\t\t}\n\t\t})\n\n\t\tr.committedFees = candidateAdd(\n\t\t\tr.committedFees, r.routeFee(rt),\n\t\t)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tr.plan = nil\n\n\tfailIndex := r.routeFailureIndex(rt, result.FailureSource)\n\n\t// Every channel before the failing outgoing channel demonstrably\n\t// carried the total load imposed by this payment's concurrent holds.\n\tfor i := 0; i < failIndex && i < len(rt.Hops); i++ {\n\t\tkey, amount, ok := r.routeEdgeAt(rt, i)\n\t\tif ok {\n\t\t\tcandidateRecordSuccess(\n\t\t\t\tkey, r.observedLoad(key, amount),\n\t\t\t)\n\t\t}\n\t}\n\n\tcode := result.Failure.Code()\n\tif key, amount, ok := r.routeEdgeAt(rt, failIndex); ok {\n\t\tload := r.observedLoad(key, amount)\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tcandidateRecordFailure(key, load)\n\t\t\tr.penalty[key] += 1.65\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\t// These indicate that this direction cannot be encoded from\n\t\t\t// the advertised policy as currently observed.\n\t\t\tr.banned[key] = true\n\n\t\tdefault:\n\t\t\tr.penalty[key] += 0.75\n\t\t}\n\t} else {\n\t\t// Sender-side refusals or unattributed failures should not poison\n\t\t// liquidity bounds, but avoiding the identical route is useful.\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\t_ lnwire.MilliSatoshi, _ int) {\n\n\t\t\tr.penalty[key] += 0.18\n\t\t})\n\t}\n\n\tr.releaseIssued(rt)\n\treturn nil\n}"
}
},
{
"id": 14,
"parent": 0,
"score": 0.3308,
"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\"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\triskPriceMsat = 420000.0\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc nodeFee(in, out *candidateEdge,\n\tforward lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif out == nil {\n\t\treturn 0\n\t}\n\n\toutFee := int64(out.fee(forward))\n\tbasis := int64(forward) + outFee\n\tinFee := in.inboundBase + basis*in.inboundRate/1_000_000\n\ttotal := outFee + inFee\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n\tepoch uint64\n}\n\nvar sharedKnowledge = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[edgeKey]liquidityBelief\n}{\n\tbeliefs: make(map[edgeKey]liquidityBelief),\n}\n\ntype localEdgeState struct {\n\tbelief liquidityBelief\n\tfailures int\n\tlastFail lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tstate map[edgeKey]*localEdgeState\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tcommittedFee lnwire.MilliSatoshi\n\treported map[*route.Route]bool\n\n\tfailureStreak int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.epoch++\n\tepoch := sharedKnowledge.epoch\n\tsharedKnowledge.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tstate: make(map[edgeKey]*localEdgeState),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\treported: make(map[*route.Route]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\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\te := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: 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\tinboundBase: int64(\n\t\t\t\t\t\tch.InboundFee.BaseFee,\n\t\t\t\t\t),\n\t\t\t\t\tinboundRate: int64(\n\t\t\t\t\t\tch.InboundFee.FeeRate,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = e\n\t\t\t\tr.incomingEdges[e.to] = append(\n\t\t\t\t\tr.incomingEdges[e.to], e,\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedKnowledge.Lock()\n\tfor key, e := range r.edgeByKey {\n\t\tb, ok := sharedKnowledge.beliefs[key]\n\t\tif !ok {\n\t\t\tb.estimate = e.capacity / 2\n\t\t}\n\t\tr.state[key] = &localEdgeState{belief: b}\n\t}\n\tsharedKnowledge.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.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.025)\n\thighMode := 0.5 / (1 + math.Exp((x-0.90)/0.035))\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif load > e.capacity {\n\t\treturn 0.005\n\t}\n\tif e.from == r.source {\n\t\tbalance := r.localBalances[e.chanID]\n\t\tif load <= balance {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := bimodalPrior(load, e.capacity)\n\tst := r.state[e.key]\n\tif st == nil || st.belief.conf <= 0 {\n\t\treturn prior\n\t}\n\n\tb := st.belief\n\tage := uint64(0)\n\tif r.epoch > b.epoch {\n\t\tage = r.epoch - b.epoch\n\t}\n\n\tconf := b.conf * math.Exp(-float64(age)/12)\n\tif conf < 0.03 {\n\t\treturn prior\n\t}\n\n\tactiveBounds := age <= 8\n\tvar learned float64\n\n\tswitch {\n\tcase activeBounds && b.lowerOK > 0 && load <= b.lowerOK:\n\t\tlearned = 0.995\n\n\tcase activeBounds && b.upperFail > 0 && load >= b.upperFail:\n\t\t// A failure may have been caused by transient reservations.\n\t\tlearned = 0.012\n\n\tdefault:\n\t\testimate := b.estimate\n\t\tif estimate <= 0 {\n\t\t\testimate = e.capacity / 2\n\t\t}\n\t\twidth := float64(e.capacity) * 0.075\n\t\tif width < 1_000_000 {\n\t\t\twidth = 1_000_000\n\t\t}\n\t\tz := (float64(load) - float64(estimate)) / width\n\t\tlearned = 1 / (1 + math.Exp(z))\n\t\tlearned = clampProbability(learned)\n\t}\n\n\treturn clampProbability(conf*learned + (1-conf)*prior)\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFee >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFee\n}\n\nfunc (r *candidateRouter) shardFeeBudget(shard,\n\tremaining lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tbudget := r.remainingFeeBudget()\n\tif budget == lnwire.MaxMilliSatoshi || shard >= remaining {\n\t\treturn budget\n\t}\n\tif remaining <= 0 {\n\t\treturn 0\n\t}\n\n\tshare := float64(budget) * float64(shard) / float64(remaining)\n\tif share >= float64(budget) {\n\t\treturn budget\n\t}\n\tif share <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(share)\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tcost float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int { return len(q) }\nfunc (q searchQueue) Less(i, j int) bool { return q[i].cost < q[j].cost }\nfunc (q searchQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }\nfunc (q *searchQueue) Push(x any) { *q = append(*q, x.(*searchItem)) }\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tfee lnwire.MilliSatoshi\n\tlogProb float64\n\tscore float64\n}\n\nfunc (r *candidateRouter) edgePenalty(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tpenalty := 0.0\n\treserved := r.reserved[e.key]\n\tif reserved > 0 {\n\t\t// Strongly prefer independent corridors for atomic shard sets.\n\t\tpenalty += 900000\n\t\tif e.capacity > 0 {\n\t\t\tpenalty += 600000 *\n\t\t\t\tfloat64(reserved) / float64(e.capacity)\n\t\t}\n\t}\n\n\tst := r.state[e.key]\n\tif st == nil || st.failures == 0 {\n\t\treturn penalty\n\t}\n\n\tunit := 650000.0\n\tif st.lastFail > 0 && load*2 < st.lastFail {\n\t\t// Preserve lower-amount retries on a failed channel.\n\t\tunit = 90000\n\t}\n\treturn penalty + float64(st.failures)*unit\n}\n\nfunc (r *candidateRouter) search(amt lnwire.MilliSatoshi,\n\triskScale float64) (*route.Route, *routeMeta, error) {\n\n\tbest := make(map[route.Vertex]float64)\n\tamount := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbest[r.spec.Target] = 0\n\tamount[r.spec.Target] = amt\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tcost: 0,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tcurrentBest, ok := best[item.node]\n\t\tif !ok || item.cost > currentBest+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tout := next[item.node]\n\t\tfor _, in := range r.incomingEdges[item.node] {\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tfee = nodeFee(in, out, item.amt)\n\t\t\t}\n\t\t\tif fee > lnwire.MaxMilliSatoshi-item.amt {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := item.amt + fee\n\t\t\tif !in.usable(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[in.key]\n\t\t\tif reserved > in.capacity ||\n\t\t\t\tover > in.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := over + reserved\n\t\t\tif in.from == r.source {\n\t\t\t\tbalance := r.localBalances[in.chanID]\n\t\t\t\tif reserved > balance ||\n\t\t\t\t\tover > balance-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(in, load)\n\t\t\tstep := float64(fee) -\n\t\t\t\triskScale*math.Log(p) + 1800\n\t\t\tstep += r.edgePenalty(in, load)\n\t\t\tnewCost := item.cost + step\n\n\t\t\told, seen := best[in.from]\n\t\t\tif seen && newCost >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[in.from] = newCost\n\t\t\tamount[in.from] = over\n\t\t\tnext[in.from] = in\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: in.from,\n\t\t\t\tcost: newCost,\n\t\t\t\tamt: over,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := best[r.source]; !ok {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfee := rt.TotalAmount - amt\n\tlogProb := 0.0\n\tfor i, e := range path {\n\t\tover := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tover = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tload := over + r.reserved[e.key]\n\t\tlogProb += math.Log(r.edgeProbability(e, load))\n\t}\n\n\treturn rt, &routeMeta{\n\t\tpath: path,\n\t\tfee: fee,\n\t\tlogProb: logProb,\n\t}, nil\n}\n\nfunc (r *candidateRouter) findRoute(amt,\n\tfeeCap lnwire.MilliSatoshi) (*route.Route, *routeMeta, error) {\n\n\tbaseScale := riskPriceMsat\n\tif feeCap != lnwire.MaxMilliSatoshi {\n\t\tbudgetScale := float64(feeCap) * 0.35\n\t\tif budgetScale < 4000 {\n\t\t\tbudgetScale = 4000\n\t\t}\n\t\tif budgetScale < baseScale {\n\t\t\tbaseScale = budgetScale\n\t\t}\n\t}\n\n\tscales := []float64{\n\t\tbaseScale,\n\t\tbaseScale / 4,\n\t\t0,\n\t\triskPriceMsat,\n\t}\n\n\tvar bestRoute *route.Route\n\tvar bestMeta *routeMeta\n\n\tfor _, scale := range scales {\n\t\trt, meta, err := r.search(amt, scale)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\tmeta.fee > feeCap {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tmeta.score = float64(meta.fee) -\n\t\t\tbaseScale*meta.logProb +\n\t\t\tfloat64(len(meta.path))*1800\n\n\t\tif bestMeta == nil || meta.score < bestMeta.score {\n\t\t\tbestRoute = rt\n\t\t\tbestMeta = meta\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\treturn bestRoute, bestMeta, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tpath := make([]*candidateEdge, 0, 12)\n\tvisited := make(map[route.Vertex]bool)\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, nil, errors.New(\"routing cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\te, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, e)\n\t\tnode = e.to\n\t}\n\n\tn := len(path)\n\tif n == 0 {\n\t\treturn nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, n)\n\texpiryOver := make([]uint32, n)\n\tamtOver[n-1] = deliver\n\texpiryOver[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := nodeFee(path[i], path[i+1], amtOver[i+1])\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, e := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < n-1 {\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: e.to,\n\t\t\tChannelID: e.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, path, nil\n}\n\nfunc ceilDiv(a lnwire.MilliSatoshi, b int) lnwire.MilliSatoshi {\n\tif b <= 1 {\n\t\treturn a\n\t}\n\td := lnwire.MilliSatoshi(b)\n\treturn (a + d - 1) / d\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 complete\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpartsLeft := int(r.spec.MaxParts - inFlightHtlcs)\n\tfactors := []float64{1}\n\tif partsLeft > 1 && r.failureStreak > 0 {\n\t\tladder := []float64{0.55, 0.32, 0.78, 1.0}\n\t\tf := ladder[(r.failureStreak-1)%len(ladder)]\n\t\tfactors = []float64{f, 1.0}\n\t}\n\n\tvar lastErr error\n\tfor _, factor := range factors {\n\t\tfor plannedParts := partsLeft; plannedParts >= 1;\n\t\t\tplannedParts-- {\n\n\t\t\tshard := ceilDiv(amt, plannedParts)\n\t\t\tif plannedParts > 1 && factor < 1 {\n\t\t\t\tscaled := lnwire.MilliSatoshi(\n\t\t\t\t\tfloat64(shard) * factor,\n\t\t\t\t)\n\t\t\t\tif scaled > 0 {\n\t\t\t\t\tshard = scaled\n\t\t\t\t}\n\t\t\t}\n\t\t\tif plannedParts == 1 {\n\t\t\t\tshard = amt\n\t\t\t}\n\t\t\tif shard > amt {\n\t\t\t\tshard = amt\n\t\t\t}\n\n\t\t\tfeeCap := r.shardFeeBudget(shard, amt)\n\t\t\trt, meta, err := r.findRoute(shard, feeCap)\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Once a corridor is strongly evidenced, allow it to\n\t\t\t// deliberately take an unequal, larger share.\n\t\t\tif plannedParts > 1 && factor == 1 &&\n\t\t\t\tmeta.logProb > math.Log(0.25) {\n\n\t\t\t\tgrown := shard + shard/2\n\t\t\t\tif grown > amt {\n\t\t\t\t\tgrown = amt\n\t\t\t\t}\n\t\t\t\tgrownCap := r.shardFeeBudget(grown, amt)\n\t\t\t\tgrt, gmeta, growErr := r.findRoute(\n\t\t\t\t\tgrown, grownCap,\n\t\t\t\t)\n\t\t\t\tif growErr == nil &&\n\t\t\t\t\tgmeta.logProb >= meta.logProb+\n\t\t\t\t\t\tmath.Log(0.65) {\n\n\t\t\t\t\trt = grt\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\nfunc routeAmountAt(rt *route.Route, index int) lnwire.MilliSatoshi {\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc routeEdgeKey(rt *route.Route, index int) edgeKey {\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\treturn edgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tif st := r.state[key]; st != nil {\n\t\tst.belief = b\n\t}\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.beliefs[key] = b\n\tsharedKnowledge.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\te := r.edgeByKey[key]\n\tst := r.state[key]\n\tif e == nil || st == nil {\n\t\treturn\n\t}\n\n\tb := st.belief\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\thighEstimate := e.capacity * 88 / 100\n\tif load > highEstimate {\n\t\thighEstimate = load\n\t}\n\tif highEstimate > b.estimate {\n\t\tb.estimate = highEstimate\n\t}\n\tif b.upperFail > 0 && load >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\tb.conf = 0.92\n\tb.epoch = r.epoch\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) recordFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\te := r.edgeByKey[key]\n\tst := r.state[key]\n\tif e == nil || st == nil {\n\t\treturn\n\t}\n\n\tst.failures++\n\tst.lastFail = load\n\n\tb := st.belief\n\tif b.upperFail == 0 || load < b.upperFail {\n\t\tb.upperFail = load\n\t}\n\n\tdepletedEstimate := load * 22 / 100\n\tif b.estimate == 0 || depletedEstimate < b.estimate {\n\t\tb.estimate = depletedEstimate\n\t}\n\n\tif b.lowerOK >= load {\n\t\t// Contradictory evidence is likely reservation or drift.\n\t\tb.lowerOK = load * 3 / 4\n\t\tb.conf = 0.68\n\t} else {\n\t\tb.conf = 0.90\n\t}\n\tif e.from == r.source {\n\t\tb.conf *= 0.8\n\t}\n\tb.epoch = r.epoch\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif rt == nil || r.reported[rt] {\n\t\treturn nil\n\t}\n\tr.reported[rt] = true\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tover := routeAmountAt(rt, i)\n\t\t\tload := over + r.reserved[key]\n\t\t\tr.recordSuccess(key, load)\n\t\t\tr.reserved[key] += over\n\t\t}\n\n\t\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\tif rt.TotalAmount > delivered {\n\t\t\tr.committedFee += rt.TotalAmount - delivered\n\t\t}\n\t\tr.failureStreak = 0\n\t\treturn nil\n\t}\n\n\tkind := fmt.Sprintf(\"%T\", result.Failure)\n\tisLiquidity := strings.Contains(\n\t\tkind, \"TemporaryChannelFailure\",\n\t)\n\tisPolicy := strings.Contains(kind, \"FeeInsufficient\") ||\n\t\tstrings.Contains(kind, \"IncorrectCltvExpiry\")\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(rt.Hops) {\n\t\tkey := routeEdgeKey(rt, failIndex)\n\t\tover := routeAmountAt(rt, failIndex)\n\t\tload := over + r.reserved[key]\n\n\t\tif isLiquidity {\n\t\t\tr.recordFailure(key, load)\n\t\t\tr.failureStreak++\n\t\t} else if isPolicy {\n\t\t\tif st := r.state[key]; st != nil {\n\t\t\t\tst.failures += 2\n\t\t\t\tst.lastFail = load\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Unknown-source liquidity failures should diversify the next route\n\t// without inventing a hard bound for every channel on the path.\n\tif isLiquidity {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tif st := r.state[key]; st != nil {\n\t\t\t\tst.failures++\n\t\t\t\tst.lastFail = routeAmountAt(rt, i) +\n\t\t\t\t\tr.reserved[key]\n\t\t\t}\n\t\t}\n\t\tr.failureStreak++\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 15,
"parent": 1,
"score": 0.4705,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(30_000)\n\n\thopCostMsat = float64(5_000)\n\treusedEdgeCostMsat = float64(1_100_000)\n\textraPartCostMsat = float64(45_000)\n\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 8\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 40\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// reserved is cumulative load contributed by successful shards in this\n\t// payment. It models both held atomic shards and liquidity consumed by\n\t// already-settled non-atomic shards.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\t// localUpper is deliberately payment-local and stricter than the shared\n\t// belief. A failed load must be retried substantially lower, rather than\n\t// being sent repeatedly at nearly the same amount.\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\t// Shared failures remain soft because another payment or held shard\n\t\t// may have caused a transient depletion.\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\n\treturn price\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tcutoff := upper * 3 / 4\n\tif r.localFails[key] >= 3 {\n\t\tcutoff = upper / 2\n\t}\n\tif cutoff <= 0 {\n\t\tcutoff = 1\n\t}\n\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\n\t\t\tif edge.from == r.source {\n\t\t\t\t// The snapshot makes the first hop nearly certain until an\n\t\t\t\t// observed local or concurrent reservation failure says\n\t\t\t\t// otherwise.\n\t\t\t\tp = 0.995\n\t\t\t\tif r.localUpper[edge.key] > 0 {\n\t\t\t\t\tp = 0.80\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.55\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tint64(total) * weight / sum,\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - 1\n\t\t}\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{40, 60}, {60, 40},\n\t\t\t{25, 75}, {75, 25},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{20, 30, 50},\n\t\t\t{50, 30, 20},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 4:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{15, 20, 25, 40},\n\t\t\t{40, 25, 20, 15},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tif parts > 4 {\n\t\t\tascending := make([]int64, parts)\n\t\t\tdescending := make([]int64, parts)\n\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tascending[i] = int64(i + 1)\n\t\t\t\tdescending[i] = int64(parts - i)\n\t\t\t}\n\n\t\t\tpatterns = append(\n\t\t\t\tpatterns,\n\t\t\t\tweightedAllocation(total, ascending),\n\t\t\t\tweightedAllocation(total, descending),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tshardCap := remainingBudget\n\n\t\t// Reserve a proportional share of a finite budget for later\n\t\t// shards. A small allowance avoids rejecting a shard merely due\n\t\t// to base-fee rounding.\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\tremainingAmount > shard {\n\n\t\t\tshardCap = remainingBudget * shard / remainingAmount\n\t\t\tallowance := remainingBudget / 20\n\t\t\tif allowance > 25_000 {\n\t\t\t\tallowance = 25_000\n\t\t\t}\n\t\t\tif shardCap <= remainingBudget-allowance {\n\t\t\t\tshardCap += allowance\n\t\t\t} else {\n\t\t\t\tshardCap = remainingBudget\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\t// When the whole route set cannot be formed, make one useful shard.\n\t// Each failure bound forces the next attempt onto another corridor or\n\t// onto a materially smaller amount.\n\tif slots > 1 {\n\t\tshard := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tfor shard > 0 {\n\t\t\tcap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tcap = feeBudget * shard / total\n\t\t\t\tif cap < 1_000 {\n\t\t\t\t\tcap = 1_000\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\n\t\t\tif shard <= 10_000 {\n\t\t\t\tbreak\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\tshard = next\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\t// MaxParts limits concurrent shards, not the lifetime number of\n\t// successful shards. Atomic held shards appear in inFlightHtlcs,\n\t// while settled non-atomic shards release their slots.\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t// A forwarding node reports failure of its outgoing edge.\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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.30\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\t// A source-side liquidity failure may be represented without a public\n\t// failure source. It is safe and useful to attribute that case to the\n\t// first channel; treating it as an uninformative dispatch refusal causes\n\t// an otherwise identical route to be retried until the runner times out.\n\tif failIdx < 0 &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure &&\n\t\tlen(meta.path) > 0 {\n\n\t\tfailIdx = 0\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t300_000 + float64(count)*175_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\t// Gossip-policy mismatches are deterministic within this\n\t\t\t// payment and should never poison liquidity estimates.\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_000_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// An unidentified non-liquidity failure provides no trustworthy\n\t// channel bound. Penalize the whole route to obtain diversity. If a\n\t// finite budget was involved, retain a small guard against dispatch\n\t// accounting or rounding differences.\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 500_000\n\t}\n\n\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\tguard := meta.fee / 20\n\t\tif guard < 1_000 {\n\t\t\tguard = 1_000\n\t\t}\n\n\t\tremaining := r.remainingFeeBudget()\n\t\tif remaining != lnwire.MaxMilliSatoshi && guard > remaining {\n\t\t\tguard = remaining\n\t\t}\n\t\tr.budgetGuard += guard\n\t}\n\n\treturn nil\n}"
},
"role": "best"
},
{
"id": 16,
"parent": 4,
"score": 0.1838,
"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\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskPriceMsat = float64(420_000)\n\thopCostMsat = float64(75_000)\n\treusedEdgeCostMsat = float64(900_000)\n\textraPartCostMsat = float64(55_000)\n\n\tmaxSearchHops = 24\n\tmaxLabelsPerNode = 12\n\tmaxRouterAttempts = 80\n\tmaxConsecutiveMisses = 52\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\n\t// The inbound fee is charged on the amount arriving at the node:\n\t// the amount forwarded onward plus the node's outbound fee.\n\tbasis := int64(amt) + outbound\n\tinbound := int64(in.inboundBase) +\n\t\tbasis*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\t// Failure bounds remain soft because held shards and background\n\t\t// traffic can make a genuine observation transient.\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tvar cutoff lnwire.MilliSatoshi\n\tswitch r.localFails[key] {\n\tcase 0, 1:\n\t\tcutoff = upper * 4 / 5\n\tcase 2:\n\t\tcutoff = upper * 2 / 3\n\tdefault:\n\t\tcutoff = upper / 2\n\t}\n\n\tif cutoff <= 0 {\n\t\treturn 1\n\t}\n\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.995\n\t\t\t}\n\n\t\t\tlocalPenalty := float64(0)\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\t// Keep a low-probability escape hatch for transient\n\t\t\t\t// self-contention, but strongly prefer a lower retry or\n\t\t\t\t// another corridor.\n\t\t\t\tratio := float64(cutoff) / float64(totalLoad)\n\t\t\t\tscale := 0.12 * ratio\n\t\t\t\tif scale < 0.015 {\n\t\t\t\t\tscale = 0.015\n\t\t\t\t}\n\t\t\t\tp *= scale\n\t\t\t\tlocalPenalty = 400_000 +\n\t\t\t\t\tfloat64(r.localFails[edge.key])*300_000\n\t\t\t}\n\n\t\t\tif p < 0.003 {\n\t\t\t\tp = 0.003\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\triskPriceMsat*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key] +\n\t\t\t\tlocalPenalty\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.60\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tremainingWeight := sum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tint64(remaining) * weight / remainingWeight,\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - 1\n\t\t}\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tremainingWeight -= weight\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{1, 7}, {7, 1},\n\t\t\t{1, 3}, {3, 1},\n\t\t\t{1, 2}, {2, 1},\n\t\t\t{2, 3}, {3, 2},\n\t\t\t{9, 11}, {11, 9},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{1, 1, 2},\n\t\t\t{2, 1, 1},\n\t\t\t{1, 2, 3},\n\t\t\t{3, 2, 1},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 4:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{1, 1, 1, 2},\n\t\t\t{2, 1, 1, 1},\n\t\t\t{1, 2, 3, 4},\n\t\t\t{4, 3, 2, 1},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tif parts > 4 {\n\t\t\tascending := make([]int64, parts)\n\t\t\tdescending := make([]int64, parts)\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tascending[i] = int64(i + 1)\n\t\t\t\tdescending[i] = int64(parts - i)\n\t\t\t}\n\n\t\t\tpatterns = append(\n\t\t\t\tpatterns,\n\t\t\t\tweightedAllocation(total, ascending),\n\t\t\t\tweightedAllocation(total, descending),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc (r *candidateRouter) informedAllocations(\n\ttotal lnwire.MilliSatoshi) [][]lnwire.MilliSatoshi {\n\n\tvar result [][]lnwire.MilliSatoshi\n\tadded := 0\n\n\taddLimit := func(limit lnwire.MilliSatoshi) {\n\t\tif added >= 16 || limit <= 0 {\n\t\t\treturn\n\t\t}\n\n\t\t// Leave room for fees and uncertainty when the limit came from a\n\t\t// first-hop balance or raw capacity.\n\t\tshard := limit * 9 / 10\n\t\tif shard <= total/10 || shard >= total*9/10 {\n\t\t\treturn\n\t\t}\n\n\t\tresult = append(result,\n\t\t\t[]lnwire.MilliSatoshi{shard, total - shard},\n\t\t\t[]lnwire.MilliSatoshi{total - shard, shard},\n\t\t)\n\t\tadded++\n\t}\n\n\t// Destination limits constrain the delivered shard directly.\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tlimit := edge.capacity\n\t\tif edge.maxHTLC > 0 && edge.maxHTLC < limit {\n\t\t\tlimit = edge.maxHTLC\n\t\t}\n\t\taddLimit(limit)\n\t}\n\n\t// First-hop balance and advertised limits often identify the useful\n\t// unequal split without spending an attempt to discover it.\n\tfor _, edge := range r.edgeLookup {\n\t\tif edge.from != r.source {\n\t\t\tcontinue\n\t\t}\n\n\t\tlimit := edge.capacity\n\t\tif edge.maxHTLC > 0 && edge.maxHTLC < limit {\n\t\t\tlimit = edge.maxHTLC\n\t\t}\n\t\tif balance, ok := r.localBalances[edge.chanID];\n\t\t\tok && balance < limit {\n\n\t\t\tlimit = balance\n\t\t}\n\t\taddLimit(limit)\n\t}\n\n\treturn result\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tshardCap := remainingBudget\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\tremainingAmount > shard {\n\n\t\t\t// Keep half of the proportional remainder in reserve. This\n\t\t\t// permits unequal path fees while preventing an early shard\n\t\t\t// from consuming the entire aggregate budget.\n\t\t\tlaterShare := remainingBudget *\n\t\t\t\t(remainingAmount - shard) / remainingAmount\n\t\t\treserve := laterShare / 2\n\t\t\tif reserve < remainingBudget {\n\t\t\t\tshardCap = remainingBudget - reserve\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) reorderPlan(plan []*plannedRoute) {\n\t// Send the hardest shard first. If it fails, replanning happens before\n\t// easier shards lock liquidity and consume MPP slots.\n\tsort.SliceStable(plan, func(i, j int) bool {\n\t\treturn plan[i].score > plan[j].score\n\t})\n\n\treservations := cloneReservations(r.reserved)\n\tfor _, pr := range plan {\n\t\tfor i, edge := range pr.meta.path {\n\t\t\tpr.meta.loads[i] =\n\t\t\t\tpr.meta.amounts[i] + reservations[edge.key]\n\t\t}\n\t\taddReservationSet(reservations, pr.meta)\n\t}\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tpatterns := allocationPatterns(total, parts)\n\t\tif parts == 2 {\n\t\t\tpatterns = append(patterns, r.informedAllocations(total)...)\n\t\t}\n\n\t\tfor _, allocation := range patterns {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\tr.reorderPlan(best)\n\t\treturn best, nil\n\t}\n\n\t// A partial shard is useful only while at least two slots remain.\n\t// Start near an equal split, then reduce materially so each failed\n\t// size explores a genuinely different liquidity region.\n\tif slots > 1 {\n\t\tshard := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tfor shard >= 10_000 {\n\t\t\tcap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tcap = feeBudget * shard / total\n\t\t\t\tallowance := feeBudget / 10\n\t\t\t\tif allowance > 20_000 {\n\t\t\t\t\tallowance = 20_000\n\t\t\t\t}\n\t\t\t\tif cap <= feeBudget-allowance {\n\t\t\t\t\tcap += allowance\n\t\t\t\t} else {\n\t\t\t\t\tcap = feeBudget\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, 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\tshard = next\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t// A forwarding node reports a failure on its outgoing edge.\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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.25\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\tif failIdx < 0 &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure &&\n\t\tlen(meta.path) > 0 {\n\n\t\tfailIdx = 0\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t\tr.edgePenalty[meta.path[i].key] *= 0.50\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t450_000 + float64(count)*275_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\t// Policy mismatches are deterministic for this payment and\n\t\t\t// must not contaminate liquidity evidence.\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 10_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_500_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Unknown failures are not liquidity evidence. They may be transient\n\t// dispatch or payment-level failures, so diversify without inventing a\n\t// hard channel bound or reducing the known fee budget.\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 900_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 17,
"parent": 4,
"score": 0.346,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(260_000)\n\n\thopCostMsat = float64(42_000)\n\treusedEdgeCostMsat = float64(950_000)\n\textraPartCostMsat = float64(105_000)\n\n\tmaxSearchHops = 32\n\tmaxLabelsPerNode = 10\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 72\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < 0.006 {\n\t\treturn 0.006\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/2400)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.018\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.018 + 0.977*pos\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt < b.upperBad:\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tswitch {\n\t\tcase ratio <= 0.55:\n\t\t\tevidence = math.Max(prior, 0.72)\n\t\tcase ratio <= 0.78:\n\t\t\tevidence = math.Max(prior, 0.38)\n\t\tdefault:\n\t\t\tevidence = math.Min(prior, 0.16)\n\t\t}\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\treturn clampProbability(prior*(1-conf) + evidence*conf)\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.52)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap)\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\n\treturn price\n}\n\nfunc (r *candidateRouter) localProbabilityFactor(key edgeKey,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tupper := r.localUpper[key]\n\tif upper <= 0 || load <= 0 {\n\t\treturn 1\n\t}\n\n\tratio := float64(load) / float64(upper)\n\tfails := r.localFails[key]\n\n\tvar factor float64\n\tswitch {\n\tcase ratio <= 0.50:\n\t\tfactor = 1\n\tcase ratio <= 0.65:\n\t\tfactor = 0.82\n\tcase ratio <= 0.78:\n\t\tfactor = 0.42\n\tcase ratio < 1:\n\t\tfactor = 0.15\n\tdefault:\n\t\tfactor = 0.045\n\t}\n\n\tif fails >= 3 && ratio > 0.65 {\n\t\tfactor *= 0.55\n\t}\n\tif fails >= 6 && ratio >= 1 {\n\t\tfactor *= 0.55\n\t}\n\n\tif factor < 0.012 {\n\t\treturn 0.012\n\t}\n\treturn factor\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.995\n\t\t\t}\n\n\t\t\tp = clampProbability(\n\t\t\t\tp * r.localProbabilityFactor(edge.key, totalLoad),\n\t\t\t)\n\n\t\t\thopCost := hopCostMsat *\n\t\t\t\t(1 + 0.025*float64(cur.hops))\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) +\n\t\t\t\thopCost +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.60\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tif len(weights) == 0 {\n\t\treturn result\n\t}\n\tif len(weights) == 1 {\n\t\tresult[0] = total\n\t\treturn result\n\t}\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tif weight > 0 {\n\t\t\tsum += weight\n\t\t}\n\t}\n\tif sum <= 0 {\n\t\treturn result\n\t}\n\n\tremaining := total\n\tremainingWeight := sum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpartsLeft := lnwire.MilliSatoshi(len(weights) - i - 1)\n\t\tif weight <= 0 {\n\t\t\tweight = 1\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tint64(remaining) * weight / remainingWeight,\n\t\t)\n\t\tmaxPart := remaining - partsLeft\n\t\tif part < 1 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part > maxPart {\n\t\t\tpart = maxPart\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tremainingWeight -= weight\n\t\tif remainingWeight <= 0 {\n\t\t\tremainingWeight = int64(len(weights) - i - 1)\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunc rotateWeights(weights []int64, shift int) []int64 {\n\tn := len(weights)\n\tout := make([]int64, n)\n\tfor i := range weights {\n\t\tout[i] = weights[(i+shift)%n]\n\t}\n\treturn out\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil\n\t}\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tweightSets := [][]int64{equal}\n\n\tif parts == 2 {\n\t\tfor first := int64(10); first <= 90; first += 10 {\n\t\t\tweightSets = append(weightSets, []int64{\n\t\t\t\tfirst, 100 - first,\n\t\t\t})\n\t\t}\n\n\t\tfor _, pair := range [][]int64{\n\t\t\t{33, 67}, {67, 33},\n\t\t\t{45, 55}, {55, 45},\n\t\t} {\n\t\t\tweightSets = append(weightSets, pair)\n\t\t}\n\t} else {\n\t\tascending := make([]int64, parts)\n\t\tdescending := make([]int64, parts)\n\t\talternating := make([]int64, parts)\n\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tascending[i] = int64(i + 1)\n\t\t\tdescending[i] = int64(parts - i)\n\t\t\tif i%2 == 0 {\n\t\t\t\talternating[i] = 1\n\t\t\t} else {\n\t\t\t\talternating[i] = 3\n\t\t\t}\n\t\t}\n\n\t\tweightSets = append(\n\t\t\tweightSets, ascending, descending, alternating,\n\t\t\trotateWeights(alternating, 1),\n\t\t)\n\n\t\tdominant := make([]int64, parts)\n\t\tfor i := range dominant {\n\t\t\tdominant[i] = 1\n\t\t}\n\t\tdominant[0] = int64(parts + 2)\n\n\t\trotations := parts\n\t\tif rotations > 6 {\n\t\t\trotations = 6\n\t\t}\n\t\tfor i := 0; i < rotations; i++ {\n\t\t\tweightSets = append(\n\t\t\t\tweightSets, rotateWeights(dominant, i),\n\t\t\t)\n\t\t}\n\n\t\tif parts == 3 {\n\t\t\tfor _, weights := range [][]int64{\n\t\t\t\t{2, 3, 5}, {5, 3, 2},\n\t\t\t\t{1, 2, 5}, {5, 2, 1},\n\t\t\t\t{2, 5, 3}, {3, 5, 2},\n\t\t\t} {\n\t\t\t\tweightSets = append(weightSets, weights)\n\t\t\t}\n\t\t}\n\n\t\tif parts == 4 {\n\t\t\tfor _, weights := range [][]int64{\n\t\t\t\t{1, 2, 3, 5}, {5, 3, 2, 1},\n\t\t\t\t{1, 1, 3, 5}, {5, 3, 1, 1},\n\t\t\t\t{2, 5, 2, 1}, {1, 2, 5, 2},\n\t\t\t} {\n\t\t\t\tweightSets = append(weightSets, weights)\n\t\t\t}\n\t\t}\n\t}\n\n\tpatterns := make([][]lnwire.MilliSatoshi, 0, len(weightSets))\n\tseen := make(map[string]bool)\n\n\tfor _, weights := range weightSets {\n\t\tallocation := weightedAllocation(total, weights)\n\t\tkey := \"\"\n\t\tvalid := true\n\t\tfor _, part := range allocation {\n\t\t\tif part <= 0 {\n\t\t\t\tvalid = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tkey += string(rune(part%2147483647)) + \":\"\n\t\t}\n\t\tif valid && !seen[key] {\n\t\t\tseen[key] = true\n\t\t\tpatterns = append(patterns, allocation)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) shardFeeCap(\n\tremainingBudget, remainingAmount,\n\tshard lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif remainingBudget == lnwire.MaxMilliSatoshi {\n\t\treturn remainingBudget\n\t}\n\tif remainingAmount <= shard {\n\t\treturn remainingBudget\n\t}\n\n\tproportional := remainingBudget * shard / remainingAmount\n\tslack := remainingBudget / 3\n\tif slack > 100_000 {\n\t\tslack = 100_000\n\t}\n\n\tif proportional >= remainingBudget-slack {\n\t\treturn remainingBudget\n\t}\n\treturn proportional + slack\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tshardCap := r.shardFeeCap(\n\t\t\tremainingBudget, remainingAmount, shard,\n\t\t)\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tmaxParts := int(slots)\n\tif lnwire.MilliSatoshi(maxParts) > total {\n\t\tmaxParts = int(total)\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\tif slots > 1 {\n\t\tbase := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tcandidates := []lnwire.MilliSatoshi{\n\t\t\tbase,\n\t\t\tbase * 5 / 6,\n\t\t\tbase * 2 / 3,\n\t\t\tbase / 2,\n\t\t\tbase / 3,\n\t\t}\n\n\t\tfor _, shard := range candidates {\n\t\t\tif shard <= 0 || shard >= total {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcap := r.shardFeeCap(feeBudget, total, shard)\n\t\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.22\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\tif failIdx < 0 &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure &&\n\t\tlen(meta.path) > 0 {\n\n\t\tfailIdx = 0\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tkey := meta.path[i].key\n\t\tupdateBelief(key, meta.loads[i], true, now)\n\t\tr.edgePenalty[key] *= 0.65\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tincrement := 220_000 + float64(count)*95_000\n\t\t\tif increment > 900_000 {\n\t\t\t\tincrement = 900_000\n\t\t\t}\n\t\t\tr.edgePenalty[edge.key] += increment\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 1_500_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 350_000\n\t}\n\n\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\tguard := meta.fee / 50\n\t\tif guard < 500 {\n\t\t\tguard = 500\n\t\t}\n\t\tif guard > 5_000 {\n\t\t\tguard = 5_000\n\t\t}\n\n\t\tremaining := r.remainingFeeBudget()\n\t\tif remaining != lnwire.MaxMilliSatoshi && guard > remaining {\n\t\t\tguard = remaining\n\t\t}\n\t\tr.budgetGuard += guard\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 18,
"parent": 4,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(30_000)\n\n\thopCostMsat = float64(18_000)\n\treusedEdgeCostMsat = float64(1_100_000)\n\textraPartCostMsat = float64(35_000)\n\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 10\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 44\n\tmaxFallbackSearches = 16\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinboundBasis := int64(amt) + outbound\n\tif inboundBasis < int64(amt) {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tinbound := int64(in.inboundBase) +\n\t\tinboundBasis*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\tif total > int64(lnwire.MaxMilliSatoshi) {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tpendingLoads map[edgeKey]lnwire.MilliSatoshi\n\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tpendingFees lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpendingLoads: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) activeReservations(\n) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := cloneReservations(r.reserved)\n\tfor key, amt := range r.pendingLoads {\n\t\tif amt > lnwire.MaxMilliSatoshi-result[key] {\n\t\t\tresult[key] = lnwire.MaxMilliSatoshi\n\t\t\tcontinue\n\t\t}\n\t\tresult[key] += amt\n\t}\n\n\treturn result\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees\n\tif r.pendingFees > lnwire.MaxMilliSatoshi-spent {\n\t\treturn 0\n\t}\n\tspent += r.pendingFees\n\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\n\treturn price\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tcutoff := upper * 4 / 5\n\tif r.localFails[key] >= 3 {\n\t\tcutoff = upper * 3 / 5\n\t}\n\tif cutoff <= 0 {\n\t\tcutoff = 1\n\t}\n\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\tif reserved > lnwire.MaxMilliSatoshi-over {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.995\n\t\t\t\tif r.localUpper[edge.key] > 0 {\n\t\t\t\t\tp = 0.82\n\t\t\t\t}\n\t\t\t\tif reserved > 0 {\n\t\t\t\t\tp *= 0.93\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reserved > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.55\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tif reservations[edge.key] >\n\t\t\tlnwire.MaxMilliSatoshi-amounts[i] {\n\n\t\t\treturn nil, errors.New(\"route load overflow\")\n\t\t}\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tremainingWeight := sum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tint64(remaining) * weight / remainingWeight,\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - 1\n\t\t}\n\t\tif part <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tremainingWeight -= weight\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil\n\t}\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{40, 60}, {60, 40},\n\t\t\t{33, 67}, {67, 33},\n\t\t\t{25, 75}, {75, 25},\n\t\t\t{15, 85}, {85, 15},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{20, 30, 50}, {50, 30, 20},\n\t\t\t{15, 25, 60}, {60, 25, 15},\n\t\t\t{20, 40, 40}, {40, 40, 20},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tascending := make([]int64, parts)\n\t\tdescending := make([]int64, parts)\n\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tascending[i] = int64(i + 1)\n\t\t\tdescending[i] = int64(parts - i)\n\t\t}\n\n\t\tpatterns = append(\n\t\t\tpatterns,\n\t\t\tweightedAllocation(total, ascending),\n\t\t\tweightedAllocation(total, descending),\n\t\t)\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) bool {\n\n\tfor i, edge := range meta.path {\n\t\tif dst[edge.key] >\n\t\t\tlnwire.MaxMilliSatoshi-meta.amounts[i] {\n\n\t\t\treturn false\n\t\t}\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := r.activeReservations()\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 ||\n\t\t\tshard > lnwire.MaxMilliSatoshi-remainingAmount {\n\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tshardCap := remainingBudget\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\tremainingAmount > shard {\n\n\t\t\tshardCap = remainingBudget * shard / remainingAmount\n\t\t\tallowance := remainingBudget / 16\n\t\t\tif allowance > 35_000 {\n\t\t\t\tallowance = 35_000\n\t\t\t}\n\n\t\t\tif shardCap <= remainingBudget-allowance {\n\t\t\t\tshardCap += allowance\n\t\t\t} else {\n\t\t\t\tshardCap = remainingBudget\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\tif !addReservationSet(reservations, pr.meta) {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\n\treturn plan, totalScore, true\n}\n\nfunc proportionalFeeCap(total, shard,\n\tbudget lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif budget == lnwire.MaxMilliSatoshi || total <= 0 {\n\t\treturn budget\n\t}\n\n\tcap := budget * shard / total\n\tallowance := budget / 12\n\tif allowance > 50_000 {\n\t\tallowance = 50_000\n\t}\n\n\tif cap <= budget-allowance {\n\t\tcap += allowance\n\t} else {\n\t\tcap = budget\n\t}\n\n\tif cap == 0 && budget > 0 {\n\t\tcap = 1\n\t}\n\n\treturn cap\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tslots uint32, feeBudget lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tdivisor := lnwire.MilliSatoshi(slots)\n\tif divisor < 2 {\n\t\tdivisor = 2\n\t}\n\n\tshard := (total + divisor - 1) / divisor\n\treservations := r.activeReservations()\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor searches := 0;\n\t\tsearches < maxFallbackSearches && shard > 0;\n\t\tsearches++ {\n\n\t\tif !seen[shard] {\n\t\t\tseen[shard] = true\n\t\t\tcap := proportionalFeeCap(total, shard, feeBudget)\n\n\t\t\tpr, err := r.findRoute(shard, reservations, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn pr, nil\n\t\t\t}\n\t\t}\n\n\t\tif shard <= 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tnext := shard * 3 / 4\n\t\tif next >= shard {\n\t\t\tnext = shard - 1\n\t\t}\n\t\tshard = next\n\t}\n\n\treturn nil, errors.New(\"no useful shard found\")\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tif len(allocation) != parts {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\tpr, err := r.fallbackShard(total, slots, feeBudget)\n\tif err == nil {\n\t\treturn []*plannedRoute{pr}, nil\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\tif pr.meta.delivered > lnwire.MaxMilliSatoshi-total {\n\t\t\treturn lnwire.MaxMilliSatoshi\n\t\t}\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) addPending(meta *routeMeta) {\n\tif meta.fee <= lnwire.MaxMilliSatoshi-r.pendingFees {\n\t\tr.pendingFees += meta.fee\n\t} else {\n\t\tr.pendingFees = lnwire.MaxMilliSatoshi\n\t}\n\n\tfor i, edge := range meta.path {\n\t\tif meta.amounts[i] >\n\t\t\tlnwire.MaxMilliSatoshi-r.pendingLoads[edge.key] {\n\n\t\t\tr.pendingLoads[edge.key] = lnwire.MaxMilliSatoshi\n\t\t\tcontinue\n\t\t}\n\t\tr.pendingLoads[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) removePending(meta *routeMeta) {\n\tif meta.fee >= r.pendingFees {\n\t\tr.pendingFees = 0\n\t} else {\n\t\tr.pendingFees -= meta.fee\n\t}\n\n\tfor i, edge := range meta.path {\n\t\told := r.pendingLoads[edge.key]\n\t\tif meta.amounts[i] >= old {\n\t\t\tdelete(r.pendingLoads, edge.key)\n\t\t\tcontinue\n\t\t}\n\t\tr.pendingLoads[edge.key] = old - meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.addPending(next.meta)\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\n\t\tload := amount\n\t\treserved := r.reserved[key]\n\t\tif reserved <= lnwire.MaxMilliSatoshi-load {\n\t\t\tload += reserved\n\t\t} else {\n\t\t\tload = lnwire.MaxMilliSatoshi\n\t\t}\n\t\tloads = append(loads, load)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\tr.removePending(meta)\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\n\t\t\tif meta.amounts[i] <=\n\t\t\t\tlnwire.MaxMilliSatoshi-r.reserved[edge.key] {\n\n\t\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\t} else {\n\t\t\t\tr.reserved[edge.key] = lnwire.MaxMilliSatoshi\n\t\t\t}\n\n\t\t\tr.edgePenalty[edge.key] *= 0.25\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tif meta.fee <= lnwire.MaxMilliSatoshi-r.committedFees {\n\t\t\tr.committedFees += meta.fee\n\t\t} else {\n\t\t\tr.committedFees = lnwire.MaxMilliSatoshi\n\t\t}\n\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t350_000 + float64(count)*225_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 10_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_500_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tpenalty := float64(850_000)\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tpenalty = 3_000_000\n\t}\n\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += penalty\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 19,
"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\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\thopCostMsat = float64(2_000)\n\treusedEdgeCostMsat = float64(650_000)\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 8\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tif outbound < 0 {\n\t\toutbound = 0\n\t}\n\n\t// The inbound fee is assessed on the outgoing amount plus the\n\t// forwarding node's outbound fee.\n\tbasis := int64(amt)\n\tif outbound <= int64(lnwire.MaxMilliSatoshi)-basis {\n\t\tbasis += outbound\n\t} else {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tinbound := int64(in.inboundBase) +\n\t\tbasis*int64(in.inboundRate)/1_000_000\n\ttotal := outbound + inbound\n\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\tif total >= int64(lnwire.MaxMilliSatoshi) {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// reserved records liquidity consumed or held by successful shards of\n\t// this payment. localUpper records failures only for this payment, so\n\t// the same load is not retried while smaller loads remain eligible.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tpolicyBlocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\n\tissuedAttempts uint32\n\tconsecutiveFailures uint32\n\tunlocatedFailures 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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpolicyBlocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\t// Cross-payment evidence stays soft because the failed liquidity\n\t\t// may have been held by another payment and may already be free.\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.policyBlocked[edge.key] ||\n\t\t\t\tcontainsNode(cur, edge.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\theld := reservations[edge.key]\n\t\t\tif held > lnwire.MaxMilliSatoshi-over {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttotalLoad := over + held\n\t\t\tif totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// A failure is a hard bound only within this payment and only\n\t\t\t// at the observed total load. Lower-load retries remain valid.\n\t\t\tif upper := r.localUpper[edge.key]; upper != 0 &&\n\t\t\t\ttotalLoad >= upper {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.985\n\t\t\t}\n\n\t\t\tscore := cur.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif held > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.45\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tif reservations[edge.key] >\n\t\t\tlnwire.MaxMilliSatoshi-amounts[i] {\n\n\t\t\treturn nil, errors.New(\"route load overflow\")\n\t\t}\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tif weight > 0 {\n\t\t\tsum += weight\n\t\t}\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tif sum <= 0 || total < lnwire.MilliSatoshi(len(weights)) {\n\t\treturn result\n\t}\n\n\tremaining := total\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tbase := total / lnwire.MilliSatoshi(sum)\n\t\tremainder := total % lnwire.MilliSatoshi(sum)\n\t\tpart := base*lnwire.MilliSatoshi(weight) +\n\t\t\tremainder*lnwire.MilliSatoshi(weight)/\n\t\t\t\tlnwire.MilliSatoshi(sum)\n\n\t\tpartsLeft := lnwire.MilliSatoshi(len(weights) - i - 1)\n\t\tif part < 1 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part > remaining-partsLeft {\n\t\t\tpart = remaining - partsLeft\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{45, 55}, {55, 45},\n\t\t\t{35, 65}, {65, 35},\n\t\t\t{25, 75}, {75, 25},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{20, 30, 50}, {50, 30, 20},\n\t\t\t{25, 35, 40}, {40, 35, 25},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 4:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{15, 20, 25, 40}, {40, 25, 20, 15},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tif parts > 4 && parts <= 8 {\n\t\t\tascending := make([]int64, parts)\n\t\t\tdescending := make([]int64, parts)\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tascending[i] = int64(i + 1)\n\t\t\t\tdescending[i] = int64(parts - i)\n\t\t\t}\n\t\t\tpatterns = append(\n\t\t\t\tpatterns,\n\t\t\t\tweightedAllocation(total, ascending),\n\t\t\t\tweightedAllocation(total, descending),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc proportionalBudget(budget, shard,\n\tremaining lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif budget == lnwire.MaxMilliSatoshi {\n\t\treturn budget\n\t}\n\tif budget <= 0 || shard <= 0 || remaining <= 0 {\n\t\treturn 0\n\t}\n\tif shard >= remaining {\n\t\treturn budget\n\t}\n\n\tcap := lnwire.MilliSatoshi(\n\t\tfloat64(budget) * float64(shard) / float64(remaining),\n\t)\n\tif cap > budget {\n\t\treturn budget\n\t}\n\treturn cap\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 ||\n\t\t\tremainingAmount > lnwire.MaxMilliSatoshi-shard {\n\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tshardCap := proportionalBudget(\n\t\t\tremainingBudget, shard, remainingAmount,\n\t\t)\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\t// Each extra part adds a base-fee surface and one more opportunity for\n\t// the changing network to invalidate the atomic set.\n\ttotalScore += float64(len(plan)-1) * 40_000\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) publicEdgeLimit(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tlimit := edge.capacity\n\tif edge.maxHTLC != 0 && edge.maxHTLC < limit {\n\t\tlimit = edge.maxHTLC\n\t}\n\n\tif upper := r.localUpper[edge.key]; upper != 0 {\n\t\tif upper <= 1 {\n\t\t\treturn 0\n\t\t}\n\t\tif upper-1 < limit {\n\t\t\tlimit = upper - 1\n\t\t}\n\t}\n\n\tif edge.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\tif !ok {\n\t\t\treturn 0\n\t\t}\n\t\tif balance < limit {\n\t\t\tlimit = balance\n\t\t}\n\t}\n\n\theld := r.reserved[edge.key]\n\tif held >= limit {\n\t\treturn 0\n\t}\n\treturn limit - held\n}\n\nfunc (r *candidateRouter) minimumPublicParts(\n\ttotal lnwire.MilliSatoshi) int {\n\n\tvar sourceMax lnwire.MilliSatoshi\n\tvar targetMax lnwire.MilliSatoshi\n\n\tfor _, edge := range r.edgeLookup {\n\t\tif r.policyBlocked[edge.key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tlimit := r.publicEdgeLimit(edge)\n\t\tif edge.from == r.source && limit > sourceMax {\n\t\t\tsourceMax = limit\n\t\t}\n\t\tif edge.to == r.spec.Target && limit > targetMax {\n\t\t\ttargetMax = limit\n\t\t}\n\t}\n\n\tmaxShard := sourceMax\n\tif targetMax < maxShard {\n\t\tmaxShard = targetMax\n\t}\n\tif maxShard <= 0 {\n\t\treturn 0\n\t}\n\n\tparts := (total-1)/maxShard + 1\n\tif parts > lnwire.MilliSatoshi(^uint(0)>>1) {\n\t\treturn 0\n\t}\n\treturn int(parts)\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tminParts := r.minimumPublicParts(total)\n\tif minParts == 0 || minParts > int(slots) {\n\t\treturn nil, errors.New(\"public htlc limits require too many parts\")\n\t}\n\tif minParts < 1 {\n\t\tminParts = 1\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\tfirstFeasible := 0\n\n\tfor parts := minParts; parts <= int(slots); parts++ {\n\t\tif firstFeasible != 0 && parts > firstFeasible+2 {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif firstFeasible == 0 {\n\t\t\t\tfirstFeasible = parts\n\t\t\t}\n\t\t\tif score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) != 0 {\n\t\treturn best, nil\n\t}\n\n\t// A partial fallback is only useful if the remaining amount can still\n\t// fit in the remaining slots. It never descends into an endless\n\t// halving ladder.\n\tif slots > 1 {\n\t\tminUseful := (total-1)/lnwire.MilliSatoshi(slots) + 1\n\t\tshardCap := proportionalBudget(\n\t\t\tfeeBudget, minUseful, total,\n\t\t)\n\n\t\tpr, err := r.findRoute(minUseful, r.reserved, shardCap)\n\t\tif err == nil {\n\t\t\treturn []*plannedRoute{pr}, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) attemptLimit(maxParts uint32) uint32 {\n\tlimit := uint32(24) + 4*maxParts\n\tif limit > 64 {\n\t\tlimit = 64\n\t}\n\treturn limit\n}\n\nfunc (r *candidateRouter) failureLimit(maxParts uint32) uint32 {\n\tlimit := uint32(12) + 3*maxParts\n\tif limit > 40 {\n\t\tlimit = 40\n\t}\n\treturn limit\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tif r.issuedAttempts >= r.attemptLimit(maxParts) {\n\t\treturn nil, errors.New(\"routing attempt limit reached\")\n\t}\n\tif r.consecutiveFailures >= r.failureLimit(maxParts) {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\tif r.unlocatedFailures >= 3 {\n\t\treturn nil, errors.New(\"repeated unclassified route failures\")\n\t}\n\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) != 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.issuedAttempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tif r.reserved[key] > lnwire.MaxMilliSatoshi-amount {\n\t\t\treturn nil\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\tr.consecutiveFailures++\n\t\tr.unlocatedFailures++\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.35\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveFailures = 0\n\t\tr.unlocatedFailures = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveFailures++\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tcode := result.Failure.Code()\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\t\tr.unlocatedFailures = 0\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tload := meta.loads[failIdx]\n\t\t\tupdateBelief(edge.key, load, false, now)\n\n\t\t\tif old := r.localUpper[edge.key]; old == 0 || load < old {\n\t\t\t\tr.localUpper[edge.key] = load\n\t\t\t}\n\t\t\tr.edgePenalty[edge.key] += 350_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\t// Public-policy disagreements are not liquidity evidence.\n\t\t\t// Retire the mismatched direction for this payment.\n\t\t\tr.policyBlocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 5_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_000_000\n\t\t}\n\t} else {\n\t\t// A refusal without a source did not probe liquidity. Tighten the\n\t\t// local budget estimate and terminate after a small bounded number\n\t\t// of such non-informative attempts.\n\t\tr.unlocatedFailures++\n\n\t\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\t\tguard := meta.fee / 20\n\t\t\tif guard < 1_000 {\n\t\t\t\tguard = 1_000\n\t\t\t}\n\n\t\t\tif r.budgetGuard <=\n\t\t\t\tlnwire.MaxMilliSatoshi-guard {\n\n\t\t\t\tr.budgetGuard += guard\n\t\t\t}\n\t\t}\n\n\t\tfor _, edge := range meta.path {\n\t\t\tr.edgePenalty[edge.key] += 250_000\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 20,
"parent": 0,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskPriceMsat = 420000.0\n\thopCostMsat = 6000.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\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) outboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tfee := int64(e.baseFeeMsat) +\n\t\tint64(amt)*int64(e.feeRatePPM)/1_000_000\n\tif fee <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) inboundFee(\n\tamt lnwire.MilliSatoshi) int64 {\n\n\treturn int64(e.inboundBase) +\n\t\tint64(amt)*int64(e.inboundRate)/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\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\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.5 * math.Exp(-x/0.06)\n\thighMode := 0.5 / (1 + math.Exp((x-0.92)/0.05))\n\tp := lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := bimodalPrior(load, e.capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[e.key]\n\tsharedBeliefs.Unlock()\n\tif !ok || b.conf == 0 {\n\t\treturn prior\n\t}\n\n\tif b.lowerOK > 0 && load <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upperFail > 0 && load >= b.upperFail {\n\t\t// A failure can be caused by a transient reservation, so an old\n\t\t// upper observation is deliberately a soft rather than hard wall.\n\t\treturn 0.012\n\t}\n\n\tif b.estimate == 0 {\n\t\treturn prior\n\t}\n\n\tscale := math.Max(float64(e.capacity)*0.10, 1_000_000)\n\tq := 1 / (1 + math.Exp(\n\t\t(float64(load)-float64(b.estimate))/scale,\n\t))\n\tweight := math.Min(0.80, b.conf/(b.conf+3))\n\tp := prior*(1-weight) + q*weight\n\tif p < 0.012 {\n\t\treturn 0.012\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc recordLiquiditySuccess(e *candidateEdge,\n\tload lnwire.MilliSatoshi) {\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[e.key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tif b.upperFail > 0 && load >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\n\thighEstimate := load + load/3\n\tif highEstimate > e.capacity {\n\t\thighEstimate = e.capacity\n\t}\n\tif b.estimate == 0 || highEstimate > b.estimate {\n\t\tb.estimate = highEstimate\n\t} else {\n\t\tb.estimate = lnwire.MilliSatoshi(\n\t\t\t(float64(b.estimate)*b.conf + float64(highEstimate)) /\n\t\t\t\t(b.conf + 1),\n\t\t)\n\t}\n\tb.conf = math.Min(12, b.conf+1)\n\tsharedBeliefs.m[e.key] = b\n}\n\nfunc recordLiquidityFailure(e *candidateEdge,\n\tload lnwire.MilliSatoshi) {\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[e.key]\n\tif b.upperFail == 0 || load < b.upperFail {\n\t\tb.upperFail = load\n\t}\n\tif b.lowerOK >= load {\n\t\tb.lowerOK = 0\n\t}\n\n\tlowEstimate := load * 2 / 5\n\tif b.estimate == 0 {\n\t\tb.estimate = lowEstimate\n\t} else {\n\t\tb.estimate = lnwire.MilliSatoshi(\n\t\t\t(float64(b.estimate)*b.conf + float64(lowEstimate)*1.5) /\n\t\t\t\t(b.conf + 1.5),\n\t\t)\n\t}\n\tb.conf = math.Min(12, b.conf+1.5)\n\tsharedBeliefs.m[e.key] = b\n}\n\ntype routeRecord struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tedgeAmts []lnwire.MilliSatoshi\n\tobserved []lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tcommitted bool\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tactive []*routeRecord\n\trecords map[*route.Route]*routeRecord\n\n\tcommittedFee lnwire.MilliSatoshi\n\tretryHint lnwire.MilliSatoshi\n\tfailPenalty map[edgeKey]int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\trecords: make(map[*route.Route]*routeRecord),\n\t\tfailPenalty: make(map[edgeKey]int),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\te := &candidateEdge{\n\t\t\t\t\tkey: edgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.\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\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], e,\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\ntype searchLabel struct {\n\tedge *candidateEdge\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\tlogProb float64\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tindex int\n}\n\ntype searchQueue []*searchLabel\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *searchQueue) Push(x any) {\n\titem := x.(*searchLabel)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype paretoPoint struct {\n\tamount lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype routeChoice struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tedgeAmts []lnwire.MilliSatoshi\n\tobserved []lnwire.MilliSatoshi\n\tprob float64\n\tfee lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) reconcileActive(inFlight uint32) {\n\tfor len(r.active) > int(inFlight) {\n\t\tremove := -1\n\t\tfor i, rec := range r.active {\n\t\t\tif rec.committed {\n\t\t\t\tremove = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif remove < 0 {\n\t\t\tremove = 0\n\t\t}\n\t\tr.active = append(\n\t\t\tr.active[:remove], r.active[remove+1:]...,\n\t\t)\n\t}\n}\n\nfunc (r *candidateRouter) reservations() map[edgeKey]lnwire.MilliSatoshi {\n\treserved := make(map[edgeKey]lnwire.MilliSatoshi)\n\tfor _, rec := range r.active {\n\t\tfor i, e := range rec.path {\n\t\t\treserved[e.key] += rec.edgeAmts[i]\n\t\t}\n\t}\n\treturn reserved\n}\n\nfunc (r *candidateRouter) reservedUncommittedFees() lnwire.MilliSatoshi {\n\tvar fees lnwire.MilliSatoshi\n\tfor _, rec := range r.active {\n\t\tif !rec.committed {\n\t\t\tfees += rec.fee\n\t\t}\n\t}\n\treturn fees\n}\n\nfunc (r *candidateRouter) remainingBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tused := r.committedFee + r.reservedUncommittedFees()\n\tif used >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - used\n}\n\nfunc (r *candidateRouter) edgeAllowed(e *candidateEdge,\n\tamt, reserved lnwire.MilliSatoshi) bool {\n\n\tif !e.usable(amt) {\n\t\treturn false\n\t}\n\tif e.key.from == r.source {\n\t\tbalance, ok := r.localBalances[e.key.chanID]\n\t\tif !ok || amt+reserved > balance {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt, reserved lnwire.MilliSatoshi) float64 {\n\n\tload := amt + reserved\n\tp := beliefProbability(e, load)\n\n\tif e.key.from == r.source {\n\t\tbalance := r.localBalances[e.key.chanID]\n\t\tif load > balance {\n\t\t\treturn 0.001\n\t\t}\n\t\tif p < 0.08 {\n\t\t\treturn 0.08\n\t\t}\n\t\tif p < 0.985 {\n\t\t\treturn p\n\t\t}\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc pathContains(path []*candidateEdge, node route.Vertex) bool {\n\tfor _, e := range path {\n\t\tif e.key.from == node || e.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addPareto(labels map[*candidateEdge][]paretoPoint,\n\te *candidateEdge, amount lnwire.MilliSatoshi, score float64) bool {\n\n\tpoints := labels[e]\n\tfor _, p := range points {\n\t\tif p.amount <= amount && p.score <= score {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := points[:0]\n\tfor _, p := range points {\n\t\tif amount <= p.amount && score <= p.score {\n\t\t\tcontinue\n\t\t}\n\t\tkept = append(kept, p)\n\t}\n\tkept = append(kept, paretoPoint{\n\t\tamount: amount,\n\t\tscore: score,\n\t})\n\tif len(kept) > 4 {\n\t\tworst := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t}\n\tlabels[e] = kept\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(deliver,\n\tfeeBudget lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tpq := &searchQueue{}\n\theap.Init(pq)\n\tlabels := make(map[*candidateEdge][]paretoPoint)\n\n\tfor _, e := range r.incomingEdges[r.spec.Target] {\n\t\tres := reserved[e.key]\n\t\tif !r.edgeAllowed(e, deliver, res) {\n\t\t\tcontinue\n\t\t}\n\n\t\tp := r.edgeProbability(e, deliver, res)\n\t\tlogProb := -math.Log(math.Max(p, 0.001))\n\t\tscore := riskPriceMsat*logProb + hopCostMsat\n\t\tif res > 0 {\n\t\t\tscore += 500000\n\t\t}\n\t\tscore += float64(r.failPenalty[e.key]) * 260000\n\n\t\tlabel := &searchLabel{\n\t\t\tedge: e,\n\t\t\tamount: deliver,\n\t\t\tscore: score,\n\t\t\tlogProb: logProb,\n\t\t\tpath: []*candidateEdge{e},\n\t\t\tamounts: []lnwire.MilliSatoshi{deliver},\n\t\t}\n\t\tif addPareto(labels, e, deliver, score) {\n\t\t\theap.Push(pq, label)\n\t\t}\n\t}\n\n\tfor pq.Len() != 0 {\n\t\tcur := heap.Pop(pq).(*searchLabel)\n\n\t\tif cur.edge.key.from == r.source {\n\t\t\tfee := cur.amount - deliver\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi &&\n\t\t\t\tfee > feeBudget {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trt, err := buildRoute(\n\t\t\t\tr.source, deliver, cur.path, cur.amounts,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tobserved := make([]lnwire.MilliSatoshi,\n\t\t\t\tlen(cur.amounts))\n\t\t\tfor i, e := range cur.path {\n\t\t\t\tobserved[i] = cur.amounts[i] +\n\t\t\t\t\treserved[e.key]\n\t\t\t}\n\n\t\t\tprob := math.Exp(-cur.logProb)\n\t\t\tif prob < 1e-12 {\n\t\t\t\tprob = 1e-12\n\t\t\t}\n\t\t\treturn &routeChoice{\n\t\t\t\trt: rt,\n\t\t\t\tpath: cur.path,\n\t\t\t\tedgeAmts: cur.amounts,\n\t\t\t\tobserved: observed,\n\t\t\t\tprob: prob,\n\t\t\t\tfee: fee,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(cur.path) >= 32 {\n\t\t\tcontinue\n\t\t}\n\n\t\tnode := cur.edge.key.from\n\t\toutFee := cur.edge.outboundFee(cur.amount)\n\n\t\tfor _, prev := range r.incomingEdges[node] {\n\t\t\tif pathContains(cur.path, prev.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tinFee := prev.inboundFee(cur.amount + outFee)\n\t\t\tnodeFee := int64(outFee) + inFee\n\t\t\tif nodeFee < 0 {\n\t\t\t\tnodeFee = 0\n\t\t\t}\n\t\t\tif nodeFee > math.MaxInt64-int64(cur.amount) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprevAmt := cur.amount +\n\t\t\t\tlnwire.MilliSatoshi(nodeFee)\n\t\t\tres := reserved[prev.key]\n\t\t\tif !r.edgeAllowed(prev, prevAmt, res) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(prev, prevAmt, res)\n\t\t\tlogProb := cur.logProb -\n\t\t\t\tmath.Log(math.Max(p, 0.001))\n\t\t\tscore := cur.score + float64(nodeFee) +\n\t\t\t\triskPriceMsat*(-math.Log(math.Max(p, 0.001))) +\n\t\t\t\thopCostMsat\n\t\t\tif res > 0 {\n\t\t\t\tscore += 500000\n\t\t\t}\n\t\t\tscore += float64(r.failPenalty[prev.key]) * 260000\n\n\t\t\tpath := make([]*candidateEdge, len(cur.path)+1)\n\t\t\tpath[0] = prev\n\t\t\tcopy(path[1:], cur.path)\n\n\t\t\tamounts := make([]lnwire.MilliSatoshi,\n\t\t\t\tlen(cur.amounts)+1)\n\t\t\tamounts[0] = prevAmt\n\t\t\tcopy(amounts[1:], cur.amounts)\n\n\t\t\tif !addPareto(labels, prev, prevAmt, score) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\theap.Push(pq, &searchLabel{\n\t\t\t\tedge: prev,\n\t\t\t\tamount: prevAmt,\n\t\t\t\tscore: score,\n\t\t\t\tlogProb: logProb,\n\t\t\t\tpath: path,\n\t\t\t\tamounts: amounts,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc buildRoute(source route.Vertex, deliver lnwire.MilliSatoshi,\n\tpath []*candidateEdge,\n\tamounts []lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 || len(path) != len(amounts) {\n\t\treturn nil, errors.New(\"invalid path\")\n\t}\n\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = finalCltvDelta\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, e := range path {\n\t\tif !e.usable(amounts[i]) {\n\t\t\treturn nil, errors.New(\"path violates htlc limits\")\n\t\t}\n\n\t\tforward := deliver\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: e.key.to,\n\t\t\tChannelID: e.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: source,\n\t\tHops: hops,\n\t}, 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 appendCandidate(dst []lnwire.MilliSatoshi,\n\tvalue, minimum, maximum 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 {\n\t\treturn dst\n\t}\n\tfor _, existing := range dst {\n\t\tif existing == value {\n\t\t\treturn dst\n\t\t}\n\t}\n\treturn append(dst, value)\n}\n\nfunc proportionalBudget(budget, shard,\n\tremaining lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif budget == lnwire.MaxMilliSatoshi {\n\t\treturn budget\n\t}\n\tif remaining <= 0 || shard >= remaining {\n\t\treturn budget\n\t}\n\treturn lnwire.MilliSatoshi(\n\t\tfloat64(budget) * float64(shard) / float64(remaining),\n\t)\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum in-flight parts reached\")\n\t}\n\n\tr.reconcileActive(inFlightHtlcs)\n\treserved := r.reservations()\n\tbudget := r.remainingBudget()\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"no part slots available\")\n\t}\n\tminimum := ceilDiv(amt, lnwire.MilliSatoshi(partsLeft))\n\n\tvar sizes []lnwire.MilliSatoshi\n\tif r.retryHint >= minimum && r.retryHint < amt {\n\t\tsizes = appendCandidate(\n\t\t\tsizes, r.retryHint, minimum, amt,\n\t\t)\n\t}\n\tsizes = appendCandidate(sizes, amt, minimum, amt)\n\tsizes = appendCandidate(sizes, amt*3/4, minimum, amt)\n\tsizes = appendCandidate(sizes, amt*2/3, minimum, amt)\n\tsizes = appendCandidate(sizes, amt/2, minimum, amt)\n\tsizes = appendCandidate(sizes, minimum*2, minimum, amt)\n\tsizes = appendCandidate(\n\t\tsizes, minimum+minimum/2, minimum, amt,\n\t)\n\tsizes = appendCandidate(sizes, minimum, minimum, amt)\n\n\tvar best *routeChoice\n\tbestUtility := math.Inf(1)\n\n\tfor _, shard := range sizes {\n\t\tshardBudget := proportionalBudget(budget, shard, amt)\n\t\tchoice, err := r.findRoute(shard, shardBudget, reserved)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tparts := float64((amt + shard - 1) / shard)\n\t\tif parts > float64(partsLeft) {\n\t\t\tcontinue\n\t\t}\n\n\t\tutility := parts*(-math.Log(choice.prob)) +\n\t\t\tparts*float64(choice.fee)/riskPriceMsat +\n\t\t\t0.10*parts\n\t\tif utility < bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\t// If a balanced atomic plan is impossible, retain a sequential,\n\t// lower-amount escape path for non-atomic payments and recovery.\n\tif best == nil && inFlightHtlcs == 0 {\n\t\tfallback := minimum\n\t\tfor i := 0; i < 4 && fallback > 1_000_000; i++ {\n\t\t\tfallback /= 2\n\t\t\tchoice, err := r.findRoute(\n\t\t\t\tfallback,\n\t\t\t\tproportionalBudget(budget, fallback, amt),\n\t\t\t\treserved,\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\tbest = choice\n\t\t\t\tbreak\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\trec := &routeRecord{\n\t\trt: best.rt,\n\t\tpath: best.path,\n\t\tedgeAmts: best.edgeAmts,\n\t\tobserved: best.observed,\n\t\tfee: best.fee,\n\t}\n\tr.records[best.rt] = rec\n\tr.active = append(r.active, rec)\n\tr.retryHint = 0\n\n\treturn best.rt, nil\n}\n\nfunc (r *candidateRouter) findRecord(rt *route.Route) *routeRecord {\n\tif rec := r.records[rt]; rec != nil {\n\t\treturn rec\n\t}\n\n\tfor candidate, rec := range r.records {\n\t\tif candidate.TotalAmount != rt.TotalAmount ||\n\t\t\tlen(candidate.Hops) != len(rt.Hops) {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tmatch := true\n\t\tfor i := range candidate.Hops {\n\t\t\tif candidate.Hops[i].ChannelID !=\n\t\t\t\trt.Hops[i].ChannelID {\n\n\t\t\t\tmatch = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif match {\n\t\t\treturn rec\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *candidateRouter) removeActive(rec *routeRecord) {\n\tfor i, active := range r.active {\n\t\tif active == rec {\n\t\t\tr.active = append(\n\t\t\t\tr.active[:i], r.active[i+1:]...,\n\t\t\t)\n\t\t\treturn\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\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\trec := r.findRecord(rt)\n\tif rec == nil {\n\t\treturn fmt.Errorf(\"unknown attempted route\")\n\t}\n\tdelete(r.records, rec.rt)\n\n\tif result.Failure == nil {\n\t\tfor i, e := range rec.path {\n\t\t\trecordLiquiditySuccess(e, rec.observed[i])\n\t\t\tif r.failPenalty[e.key] > 0 {\n\t\t\t\tr.failPenalty[e.key]--\n\t\t\t}\n\t\t}\n\t\tif !rec.committed {\n\t\t\tr.committedFee += rec.fee\n\t\t\trec.committed = true\n\t\t}\n\t\treturn nil\n\t}\n\n\tfailIdx := failureEdgeIndex(rt, result.FailureSource)\n\tif failIdx > len(rec.path) {\n\t\tfailIdx = -1\n\t}\n\n\t// Every edge before the failing node demonstrably carried the HTLC.\n\tif failIdx >= 0 {\n\t\tfor i := 0; i < failIdx && i < len(rec.path); i++ {\n\t\t\trecordLiquiditySuccess(\n\t\t\t\trec.path[i], rec.observed[i],\n\t\t\t)\n\t\t}\n\t}\n\n\tcode := result.Failure.Code()\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif failIdx >= 0 && failIdx < len(rec.path) {\n\t\t\te := rec.path[failIdx]\n\t\t\trecordLiquidityFailure(e, rec.observed[failIdx])\n\t\t\tr.failPenalty[e.key] += 3\n\t\t} else {\n\t\t\tfor _, e := range rec.path {\n\t\t\t\tr.failPenalty[e.key]++\n\t\t\t}\n\t\t}\n\n\t\tdelivered := rec.edgeAmts[len(rec.edgeAmts)-1]\n\t\tretry := delivered * 11 / 20\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\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry,\n\t\tlnwire.CodeExpiryTooSoon:\n\n\t\tif failIdx >= 0 && failIdx < len(rec.path) {\n\t\t\tr.failPenalty[rec.path[failIdx].key] += 5\n\t\t} else {\n\t\t\tfor _, e := range rec.path {\n\t\t\t\tr.failPenalty[e.key] += 2\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\tif failIdx >= 0 && failIdx < len(rec.path) {\n\t\t\tr.failPenalty[rec.path[failIdx].key] += 2\n\t\t} else {\n\t\t\tfor _, e := range rec.path {\n\t\t\t\tr.failPenalty[e.key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tr.removeActive(rec)\n\treturn nil\n}"
}
},
{
"id": 21,
"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\"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\tcandidateFinalCLTV = uint32(40)\n\tcandidateRiskPrice = 420000.0\n\tcandidateHopPrice = 30000.0\n\tcandidatePartPrice = 140000.0\n\tcandidateFeeWeight = 1.25\n\tcandidateReuseNats = 2.10\n\tcandidateMinShard = lnwire.MilliSatoshi(10000)\n\tcandidateProbFloor = 0.012\n\tcandidateProbCeiling = 0.995\n\tcandidateMaxHops = 20\n\tcandidateMaxLabels = 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 int64\n\tfeeRatePPM int64\n\tcltvDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc (e *candidateEdge) outgoingFee(amt lnwire.MilliSatoshi) int64 {\n\tfee := e.baseFeeMsat + int64(amt)*e.feeRatePPM/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn fee\n}\n\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif incoming == nil || outgoing == nil {\n\t\treturn 0\n\t}\n\n\toutFee := outgoing.outgoingFee(amt)\n\tinBasis := int64(amt) + outFee\n\tinFee := incoming.inboundBase +\n\t\tinBasis*incoming.inboundRate/1_000_000\n\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tsuccess uint32\n\tfailure uint32\n}\n\nvar candidateSharedBeliefs = struct {\n\tsync.Mutex\n\tm map[candidateEdgeKey]candidateBelief\n}{\n\tm: make(map[candidateEdgeKey]candidateBelief),\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn candidateProbFloor\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.045)\n\thighMode := 0.5 / (1 + math.Exp((x-0.94)/0.035))\n\tp := lowMode + highMode\n\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc candidateProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := candidatePrior(load, e.capacity)\n\n\tcandidateSharedBeliefs.Lock()\n\tbelief, ok := candidateSharedBeliefs.m[e.key]\n\tcandidateSharedBeliefs.Unlock()\n\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn candidateProbCeiling\n\t}\n\n\tif belief.upperBad > 0 && load >= belief.upperBad {\n\t\tp := prior * 0.08\n\t\tif p < candidateProbFloor {\n\t\t\tp = candidateProbFloor\n\t\t}\n\t\treturn p\n\t}\n\n\tobservations := float64(belief.success + belief.failure)\n\tobservationConfidence := 1 - math.Exp(-observations/3.0)\n\n\tvar learned, confidence float64\n\n\tswitch {\n\tcase belief.lowerOK > 0 && belief.upperBad > belief.lowerOK:\n\t\tmid := 0.5 * float64(belief.lowerOK+belief.upperBad)\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad-belief.lowerOK)*0.16,\n\t\t\tfloat64(e.capacity)*0.01,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(load)-mid)/width,\n\t\t))\n\t\tconfidence = 0.68 + 0.27*observationConfidence\n\n\tcase belief.upperBad > 0:\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad)*0.12,\n\t\t\tfloat64(e.capacity)*0.012,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(load)-float64(belief.upperBad))/width,\n\t\t))\n\t\tconfidence = 0.55 + 0.30*observationConfidence\n\n\tcase belief.lowerOK > 0:\n\t\tif load <= belief.lowerOK {\n\t\t\tlearned = candidateProbCeiling\n\t\t} else {\n\t\t\texcess := float64(load-belief.lowerOK) /\n\t\t\t\tmath.Max(float64(e.capacity), 1)\n\t\t\tlearned = candidateProbCeiling *\n\t\t\t\tmath.Exp(-excess/0.14)\n\t\t}\n\t\tconfidence = 0.48 + 0.28*observationConfidence\n\n\tdefault:\n\t\treturn prior\n\t}\n\n\tp := confidence*learned + (1-confidence)*prior\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > candidateProbCeiling {\n\t\treturn candidateProbCeiling\n\t}\n\treturn p\n}\n\nfunc candidateRecordSuccess(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\n\t// A success at or above an earlier failure contradicts that failure.\n\t// This is common when a temporary concurrent hold has been released.\n\tif b.upperBad > 0 && load >= b.upperBad {\n\t\tb.upperBad = 0\n\t}\n\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tb.success++\n\tcandidateSharedBeliefs.m[key] = b\n}\n\nfunc candidateRecordFailure(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\n\t// Do not preserve a stale hard success bound across a contradictory\n\t// failure. Amounts strictly below the failure remain plausible.\n\tif b.lowerOK >= load {\n\t\tif load > 1 {\n\t\t\tb.lowerOK = load - 1\n\t\t} else {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tif b.upperBad == 0 || load < b.upperBad {\n\t\tb.upperBad = load\n\t}\n\tb.failure++\n\tcandidateSharedBeliefs.m[key] = b\n}\n\ntype candidateSearchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk float64\n\thops int\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype candidateSearchQueue []*candidateSearchItem\n\nfunc (q candidateSearchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateSearchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateSearchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateSearchQueue) Push(x any) {\n\titem := x.(*candidateSearchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateSearchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype candidateLabel struct {\n\tamount lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocal map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route]bool\n\tpenalty map[candidateEdgeKey]float64\n\tbanned map[candidateEdgeKey]bool\n\n\tplan []*route.Route\n\tcommittedFees lnwire.MilliSatoshi\n\tfailures 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[candidateEdgeKey]*candidateEdge),\n\t\tlocal: localBalances,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route]bool),\n\t\tpenalty: make(map[candidateEdgeKey]float64),\n\t\tbanned: make(map[candidateEdgeKey]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\n\t\t\t\t\tbaseFeeMsat: int64(policy.FeeBaseMSat),\n\t\t\t\t\tfeeRatePPM: int64(\n\t\t\t\t\t\tpolicy.FeeProportionalMillionths,\n\t\t\t\t\t),\n\t\t\t\t\tcltvDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\tr.edges[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) budgetLeft() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateAdd(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b > 0 && a > lnwire.MaxMilliSatoshi-b {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn a + b\n}\n\nfunc candidatePathContains(path []*candidateEdge,\n\tnode route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateDominated(labels []candidateLabel,\n\tamount lnwire.MilliSatoshi, score float64) bool {\n\n\tfor _, label := range labels {\n\t\tif label.amount <= amount && label.score <= score {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(labels []candidateLabel,\n\tamount lnwire.MilliSatoshi, score float64) []candidateLabel {\n\n\tfiltered := labels[:0]\n\tfor _, label := range labels {\n\t\tif amount <= label.amount && score <= label.score {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, label)\n\t}\n\n\tfiltered = append(filtered, candidateLabel{\n\t\tamount: amount,\n\t\tscore: score,\n\t})\n\n\tif len(filtered) <= candidateMaxLabels {\n\t\treturn filtered\n\t}\n\n\tworst := 0\n\tfor i := 1; i < len(filtered); i++ {\n\t\tif filtered[i].score > filtered[worst].score {\n\t\t\tworst = i\n\t\t}\n\t}\n\n\tfiltered[worst] = filtered[len(filtered)-1]\n\treturn filtered[:len(filtered)-1]\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi) (*route.Route, float64, float64, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, 0, 0, errors.New(\"invalid shard amount\")\n\t}\n\n\tlabels := make(map[route.Vertex][]candidateLabel)\n\tlabels[r.spec.Target] = []candidateLabel{{\n\t\tamount: deliver,\n\t\tscore: 0,\n\t}}\n\n\tpq := &candidateSearchQueue{}\n\theap.Push(pq, &candidateSearchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateSearchItem)\n\n\t\tif candidateDominated(\n\t\t\tlabels[item.node], item.amount, item.score-0.0001,\n\t\t) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item.path, deliver)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, 0, err\n\t\t\t}\n\t\t\tif r.routeFee(rt) > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treturn rt, math.Exp(-item.risk), item.score, nil\n\t\t}\n\n\t\tif item.hops >= candidateMaxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar outgoing *candidateEdge\n\t\tif len(item.path) > 0 {\n\t\t\toutgoing = item.path[0]\n\t\t}\n\n\t\tfor _, incoming := range r.incoming[item.node] {\n\t\t\tif r.banned[incoming.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif candidatePathContains(item.path, incoming.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOver := item.amount\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tamountOver = candidateAdd(\n\t\t\t\t\tamountOver,\n\t\t\t\t\tforwardingFee(incoming, outgoing, item.amount),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif !incoming.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi &&\n\t\t\t\tamountOver-deliver > maxFee {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := candidateAdd(\n\t\t\t\tamountOver, r.reserved[incoming.key],\n\t\t\t)\n\t\t\tif extra != nil {\n\t\t\t\tload = candidateAdd(load, extra[incoming.key])\n\t\t\t}\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar probability float64\n\t\t\tif incoming.key.from == r.source {\n\t\t\t\tbalance, ok := r.local[incoming.key.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprobability = 0.999\n\t\t\t} else {\n\t\t\t\tprobability = candidateProbability(incoming, load)\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability)\n\t\t\treuse := 0.0\n\t\t\tif r.reserved[incoming.key] > 0 ||\n\t\t\t\t(extra != nil && extra[incoming.key] > 0) {\n\n\t\t\t\treuse = candidateReuseNats\n\t\t\t}\n\n\t\t\tfeeIncrement := float64(amountOver - item.amount)\n\t\t\tnewScore := item.score +\n\t\t\t\tcandidateFeeWeight*feeIncrement +\n\t\t\t\tcandidateRiskPrice*(\n\t\t\t\t\trisk+r.penalty[incoming.key]+reuse,\n\t\t\t\t) +\n\t\t\t\tcandidateHopPrice\n\n\t\t\tif candidateDominated(\n\t\t\t\tlabels[incoming.key.from], amountOver, newScore,\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = incoming\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tlabels[incoming.key.from] = candidateInsertLabel(\n\t\t\t\tlabels[incoming.key.from], amountOver, newScore,\n\t\t\t)\n\t\t\theap.Push(pq, &candidateSearchItem{\n\t\t\t\tnode: incoming.key.from,\n\t\t\t\tamount: amountOver,\n\t\t\t\tscore: newScore,\n\t\t\t\trisk: item.risk + risk,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tdeliver lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = candidateFinalCLTV\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := forwardingFee(path[i], path[i+1], amounts[i+1])\n\t\tamounts[i] = candidateAdd(amounts[i+1], fee)\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].cltvDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := candidateFinalCLTV\n\t\tif i < n-1 {\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) forEachRouteEdge(rt *route.Route,\n\tfn func(candidateEdgeKey, lnwire.MilliSatoshi, int)) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tfn(key, amount, i)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc (r *candidateRouter) addVirtualReservation(\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\treserved[key] = candidateAdd(reserved[key], amount)\n\t})\n}\n\nfunc (r *candidateRouter) reserveIssued(rt *route.Route) {\n\tif r.issued[rt] {\n\t\treturn\n\t}\n\n\tr.issued[rt] = true\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tr.reserved[key] = candidateAdd(r.reserved[key], amount)\n\t})\n}\n\nfunc (r *candidateRouter) releaseIssued(rt *route.Route) {\n\tif !r.issued[rt] {\n\t\treturn\n\t}\n\n\tdelete(r.issued, rt)\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tcurrent := r.reserved[key]\n\t\tif current <= amount {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = current - amount\n\t\t}\n\t})\n}\n\nfunc (r *candidateRouter) maxFeeForPlan(\n\talready lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tleft := r.budgetLeft()\n\tif left == lnwire.MaxMilliSatoshi {\n\t\treturn left\n\t}\n\tif already >= left {\n\t\treturn 0\n\t}\n\treturn left - already\n}\n\nfunc candidateEqualWeights(parts int) []int {\n\tweights := make([]int, parts)\n\tfor i := range weights {\n\t\tweights[i] = 1\n\t}\n\treturn weights\n}\n\nfunc candidateRotateWeights(weights []int, shift int) []int {\n\trotated := make([]int, len(weights))\n\tfor i := range weights {\n\t\trotated[i] = weights[(i+shift)%len(weights)]\n\t}\n\treturn rotated\n}\n\nfunc candidateWeightPlans(parts int) [][]int {\n\tif parts == 1 {\n\t\treturn [][]int{{1}}\n\t}\n\n\tif parts == 2 {\n\t\treturn [][]int{\n\t\t\t{1, 1},\n\t\t\t{11, 9}, {9, 11},\n\t\t\t{3, 2}, {2, 3},\n\t\t\t{2, 1}, {1, 2},\n\t\t\t{3, 1}, {1, 3},\n\t\t\t{4, 1}, {1, 4},\n\t\t}\n\t}\n\n\tplans := [][]int{candidateEqualWeights(parts)}\n\tbases := [][]int{\n\t\tcandidateEqualWeights(parts),\n\t\tcandidateEqualWeights(parts),\n\t}\n\n\tbases[0][0] = 2\n\tbases[1][0] = 3\n\tif parts > 2 {\n\t\tbases[1][1] = 2\n\t}\n\n\tfor _, base := range bases {\n\t\tfor shift := 0; shift < parts; shift++ {\n\t\t\tplans = append(\n\t\t\t\tplans, candidateRotateWeights(base, shift),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn plans\n}\n\nfunc candidatePartition(amount lnwire.MilliSatoshi,\n\tweights []int) ([]lnwire.MilliSatoshi, bool) {\n\n\tparts := len(weights)\n\tif parts == 0 ||\n\t\tamount < candidateMinShard*lnwire.MilliSatoshi(parts) {\n\n\t\treturn nil, false\n\t}\n\n\tremaining := amount\n\tremainingWeight := 0\n\tfor _, weight := range weights {\n\t\tif weight <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\t\tremainingWeight += weight\n\t}\n\n\tshards := make([]lnwire.MilliSatoshi, parts)\n\tfor i, weight := range weights {\n\t\tleft := parts - i\n\t\tif left == 1 {\n\t\t\tshards[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tshard := remaining *\n\t\t\tlnwire.MilliSatoshi(weight) /\n\t\t\tlnwire.MilliSatoshi(remainingWeight)\n\n\t\tmaximum := remaining -\n\t\t\tcandidateMinShard*lnwire.MilliSatoshi(left-1)\n\t\tif shard < candidateMinShard {\n\t\t\tshard = candidateMinShard\n\t\t}\n\t\tif shard > maximum {\n\t\t\tshard = maximum\n\t\t}\n\n\t\tshards[i] = shard\n\t\tremaining -= shard\n\t\tremainingWeight -= weight\n\t}\n\n\treturn shards, true\n}\n\nfunc (r *candidateRouter) tryPlan(amount lnwire.MilliSatoshi,\n\tweights []int) ([]*route.Route, float64, bool) {\n\n\tshards, ok := candidatePartition(amount, weights)\n\tif !ok {\n\t\treturn nil, 0, false\n\t}\n\n\tvirtual := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tplannedFees := lnwire.MilliSatoshi(0)\n\tplan := make([]*route.Route, 0, len(shards))\n\ttotalScore := candidatePartPrice * float64(len(shards)-1)\n\n\tfor _, shard := range shards {\n\t\tmaxFee := r.maxFeeForPlan(plannedFees)\n\t\trt, _, score, err := r.findRoute(shard, virtual, maxFee)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tif r.budgetLeft() != lnwire.MaxMilliSatoshi &&\n\t\t\tplannedFees+fee > r.budgetLeft() {\n\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tplan = append(plan, rt)\n\t\ttotalScore += score\n\t\tplannedFees += fee\n\t\tr.addVirtualReservation(virtual, rt)\n\t}\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(amount lnwire.MilliSatoshi,\n\tslots uint32) ([]*route.Route, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tmaxParts := int(slots)\n\tif maxParts > 8 {\n\t\tmaxParts = 8\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tif amount <\n\t\t\tcandidateMinShard*lnwire.MilliSatoshi(parts) {\n\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, weights := range candidateWeightPlans(parts) {\n\t\t\tplan, score, ok := r.tryPlan(amount, weights)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// After failures, modestly favor a split plan. It permits\n\t\t\t// retries below a learned upper bound without forcing a\n\t\t\t// long reactive halving ladder.\n\t\t\tif r.failures > 0 && parts > 1 {\n\t\t\t\tdiscount := math.Min(\n\t\t\t\t\tfloat64(r.failures), 4,\n\t\t\t\t) * 45000\n\t\t\t\tscore -= discount\n\t\t\t}\n\n\t\t\tif score < bestScore {\n\t\t\t\tbestScore = score\n\t\t\t\tbestPlan = plan\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(bestPlan) != 0 {\n\t\treturn bestPlan, nil\n\t}\n\n\t// If the full amount cannot be jointly allocated, try a useful\n\t// evidence-gathering shard. This also handles non-atomic runners\n\t// where successful shards immediately reduce the remainder.\n\tdivisors := []lnwire.MilliSatoshi{2, 3, 4, 6, 8, 12, 16}\n\tfor _, divisor := range divisors {\n\t\tshard := amount / divisor\n\t\tif shard < candidateMinShard {\n\t\t\tcontinue\n\t\t}\n\n\t\trt, _, _, err := r.findRoute(\n\t\t\tshard, nil, r.budgetLeft(),\n\t\t)\n\t\tif err == nil {\n\t\t\treturn []*route.Route{rt}, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route plan found\")\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tfor {\n\t\tif amt <= 0 {\n\t\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t\t}\n\n\t\tif len(r.plan) == 0 {\n\t\t\tmaxParts := r.spec.MaxParts\n\t\t\tif maxParts == 0 {\n\t\t\t\tmaxParts = 1\n\t\t\t}\n\t\t\tif inFlightHtlcs >= maxParts {\n\t\t\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t\t\t}\n\n\t\t\tplan, err := r.makePlan(\n\t\t\t\tamt, maxParts-inFlightHtlcs,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr.plan = plan\n\t\t}\n\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tif r.routeAmount(rt) > amt {\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tbudget := r.budgetLeft()\n\t\tif budget != lnwire.MaxMilliSatoshi && fee > budget {\n\t\t\tr.plan = nil\n\t\t\tr.failures++\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserveIssued(rt)\n\t\treturn rt, nil\n\t}\n}\n\nfunc (r *candidateRouter) routeFailureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) routeEdgeAt(rt *route.Route,\n\tindex int) (candidateEdgeKey, lnwire.MilliSatoshi, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tamount := rt.TotalAmount\n\tif index > 0 {\n\t\tamount = rt.Hops[index-1].AmtToForward\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, amount, true\n}\n\nfunc (r *candidateRouter) observedLoad(key candidateEdgeKey,\n\tfallback lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif load := r.reserved[key]; load > 0 {\n\t\treturn load\n\t}\n\treturn fallback\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\t\tcandidateRecordSuccess(\n\t\t\t\tkey, r.observedLoad(key, amount),\n\t\t\t)\n\t\t\tif r.penalty[key] > 0.35 {\n\t\t\t\tr.penalty[key] -= 0.35\n\t\t\t} else {\n\t\t\t\tdelete(r.penalty, key)\n\t\t\t}\n\t\t})\n\n\t\tr.committedFees = candidateAdd(\n\t\t\tr.committedFees, r.routeFee(rt),\n\t\t)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tr.plan = nil\n\n\tfailIndex := r.routeFailureIndex(rt, result.FailureSource)\n\n\t// All earlier channels demonstrably carried their aggregate load.\n\tfor i := 0; i < failIndex && i < len(rt.Hops); i++ {\n\t\tkey, amount, ok := r.routeEdgeAt(rt, i)\n\t\tif ok {\n\t\t\tcandidateRecordSuccess(\n\t\t\t\tkey, r.observedLoad(key, amount),\n\t\t\t)\n\t\t}\n\t}\n\n\t// Encourage a genuinely different route, rather than cycling through\n\t// the same long prefix while only changing its final edge.\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t_ lnwire.MilliSatoshi, _ int) {\n\n\t\tr.penalty[key] += 0.06\n\t})\n\n\tcode := result.Failure.Code()\n\tif key, amount, ok := r.routeEdgeAt(rt, failIndex); ok {\n\t\tload := r.observedLoad(key, amount)\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tcandidateRecordFailure(key, load)\n\t\t\tr.penalty[key] += 1.45\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.banned[key] = true\n\n\t\tdefault:\n\t\t\tr.penalty[key] += 0.75\n\t\t}\n\t} else {\n\t\t// An unattributed failure may be a transient sender-side refusal.\n\t\t// Penalize the attempted route softly without poisoning liquidity.\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\t_ lnwire.MilliSatoshi, _ int) {\n\n\t\t\tr.penalty[key] += 0.14\n\t\t})\n\t}\n\n\tr.releaseIssued(rt)\n\treturn nil\n}"
}
},
{
"id": 22,
"parent": 3,
"score": 0.1349,
"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\"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\triskPriceMsat = 420000.0\n\tmaxAttempts = 96\n\tbeamWidth = 12\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc nodeFee(in, out *candidateEdge,\n\tforward lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif out == nil {\n\t\treturn 0\n\t}\n\n\toutFee := int64(out.fee(forward))\n\tbasis := int64(forward) + outFee\n\tinFee := in.inboundBase + basis*in.inboundRate/1_000_000\n\ttotal := outFee + inFee\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n\tepoch uint64\n}\n\nvar sharedKnowledge = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[edgeKey]liquidityBelief\n}{\n\tbeliefs: make(map[edgeKey]liquidityBelief),\n}\n\ntype localEdgeState struct {\n\tbelief liquidityBelief\n\tfailures int\n\tuses int\n\tlastFail lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tstate map[edgeKey]*localEdgeState\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tcommittedFee lnwire.MilliSatoshi\n\treported map[*route.Route]bool\n\n\tplan []*route.Route\n\tattempts int\n\tfailureStreak int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.epoch++\n\tepoch := sharedKnowledge.epoch\n\tsharedKnowledge.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tstate: make(map[edgeKey]*localEdgeState),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\treported: make(map[*route.Route]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\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\te := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: 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\tinboundBase: int64(\n\t\t\t\t\t\tch.InboundFee.BaseFee,\n\t\t\t\t\t),\n\t\t\t\t\tinboundRate: int64(\n\t\t\t\t\t\tch.InboundFee.FeeRate,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = e\n\t\t\t\tr.incomingEdges[e.to] = append(\n\t\t\t\t\tr.incomingEdges[e.to], e,\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedKnowledge.Lock()\n\tfor key, e := range r.edgeByKey {\n\t\tb, ok := sharedKnowledge.beliefs[key]\n\t\tif !ok {\n\t\t\tb.estimate = e.capacity / 2\n\t\t}\n\t\tr.state[key] = &localEdgeState{belief: b}\n\t}\n\tsharedKnowledge.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.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.025)\n\thighMode := 0.5 / (1 + math.Exp((x-0.90)/0.035))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif load > e.capacity {\n\t\treturn 0.005\n\t}\n\n\tif e.from == r.source {\n\t\tif load <= r.localBalances[e.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := bimodalPrior(load, e.capacity)\n\tst := r.state[e.key]\n\tif st == nil || st.belief.conf <= 0 {\n\t\treturn prior\n\t}\n\n\tb := st.belief\n\tvar age uint64\n\tif r.epoch > b.epoch {\n\t\tage = r.epoch - b.epoch\n\t}\n\n\tconf := b.conf * math.Exp(-float64(age)/14)\n\tif conf < 0.025 {\n\t\treturn prior\n\t}\n\n\tvar learned float64\n\tswitch {\n\tcase age <= 10 && b.lowerOK > 0 && load <= b.lowerOK:\n\t\tlearned = 0.995\n\n\tcase age <= 10 && b.upperFail > 0 && load >= b.upperFail:\n\t\tlearned = 0.012\n\n\tdefault:\n\t\testimate := b.estimate\n\t\tif estimate <= 0 {\n\t\t\testimate = e.capacity / 2\n\t\t}\n\n\t\twidth := float64(e.capacity) * 0.075\n\t\tif width < 1_000_000 {\n\t\t\twidth = 1_000_000\n\t\t}\n\n\t\tz := (float64(load) - float64(estimate)) / width\n\t\tlearned = clampProbability(1 / (1 + math.Exp(z)))\n\t}\n\n\treturn clampProbability(conf*learned + (1-conf)*prior)\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFee >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFee\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tcost float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].cost < q[j].cost\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchItem))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tfee lnwire.MilliSatoshi\n\tlogProb float64\n\tscore float64\n}\n\nfunc (r *candidateRouter) edgePenalty(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tst := r.state[e.key]\n\tpenalty := 0.0\n\n\treserved := r.reserved[e.key]\n\tif reserved > 0 {\n\t\tpenalty += 760000\n\t\tif e.capacity > 0 {\n\t\t\tpenalty += 520000 *\n\t\t\t\tfloat64(reserved) / float64(e.capacity)\n\t\t}\n\t}\n\n\tif st == nil {\n\t\treturn penalty\n\t}\n\n\tpenalty += float64(st.uses) * 55000\n\n\tif st.failures == 0 {\n\t\treturn penalty\n\t}\n\n\tunit := 480000.0\n\tif st.lastFail > 0 && load*2 < st.lastFail {\n\t\tunit = 70000\n\t} else if st.lastFail > 0 && load*4 < st.lastFail*3 {\n\t\tunit = 190000\n\t}\n\n\treturn penalty + float64(st.failures)*unit\n}\n\nfunc (r *candidateRouter) search(amt lnwire.MilliSatoshi,\n\triskScale float64) (*route.Route, *routeMeta, error) {\n\n\tbest := make(map[route.Vertex]float64)\n\tamount := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbest[r.spec.Target] = 0\n\tamount[r.spec.Target] = amt\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tcost: 0,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tcurrentBest, ok := best[item.node]\n\t\tif !ok || item.cost > currentBest+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tout := next[item.node]\n\t\tfor _, in := range r.incomingEdges[item.node] {\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tfee = nodeFee(in, out, item.amt)\n\t\t\t}\n\t\t\tif fee > lnwire.MaxMilliSatoshi-item.amt {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := item.amt + fee\n\t\t\tif !in.usable(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[in.key]\n\t\t\tif reserved > in.capacity ||\n\t\t\t\tover > in.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := over + reserved\n\t\t\tif in.from == r.source {\n\t\t\t\tbalance := r.localBalances[in.chanID]\n\t\t\t\tif reserved > balance ||\n\t\t\t\t\tover > balance-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(in, load)\n\t\t\tstep := float64(fee) -\n\t\t\t\triskScale*math.Log(p) + 1800\n\t\t\tstep += r.edgePenalty(in, load)\n\n\t\t\tnewCost := item.cost + step\n\t\t\told, exists := best[in.from]\n\t\t\tif exists && newCost >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[in.from] = newCost\n\t\t\tamount[in.from] = over\n\t\t\tnext[in.from] = in\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: in.from,\n\t\t\t\tcost: newCost,\n\t\t\t\tamt: over,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := best[r.source]; !ok {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfee := rt.TotalAmount - amt\n\tlogProb := 0.0\n\tfor i, e := range path {\n\t\tload := routeAmountAt(rt, i) + r.reserved[e.key]\n\t\tlogProb += math.Log(r.edgeProbability(e, load))\n\t}\n\n\treturn rt, &routeMeta{\n\t\tpath: path,\n\t\tfee: fee,\n\t\tlogProb: logProb,\n\t}, nil\n}\n\nfunc (r *candidateRouter) findRoute(amt,\n\tfeeCap lnwire.MilliSatoshi) (*route.Route, *routeMeta, error) {\n\n\tbaseScale := riskPriceMsat\n\tif feeCap != lnwire.MaxMilliSatoshi {\n\t\tbudgetScale := float64(feeCap) * 0.45\n\t\tif budgetScale < 4000 {\n\t\t\tbudgetScale = 4000\n\t\t}\n\t\tif budgetScale < baseScale {\n\t\t\tbaseScale = budgetScale\n\t\t}\n\t}\n\n\tscales := []float64{\n\t\tbaseScale,\n\t\tbaseScale / 4,\n\t\t0,\n\t\triskPriceMsat,\n\t}\n\n\tvar bestRoute *route.Route\n\tvar bestMeta *routeMeta\n\n\tfor _, scale := range scales {\n\t\trt, meta, err := r.search(amt, scale)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\tmeta.fee > feeCap {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tmeta.score = float64(meta.fee) -\n\t\t\tbaseScale*meta.logProb +\n\t\t\tfloat64(len(meta.path))*1800\n\n\t\tif bestMeta == nil || meta.score < bestMeta.score {\n\t\t\tbestRoute = rt\n\t\t\tbestMeta = meta\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\treturn bestRoute, bestMeta, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tpath := make([]*candidateEdge, 0, 12)\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, nil, errors.New(\"routing cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\te, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, e)\n\t\tnode = e.to\n\t}\n\n\tn := len(path)\n\tif n == 0 {\n\t\treturn nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, n)\n\texpiryOver := make([]uint32, n)\n\tamtOver[n-1] = deliver\n\texpiryOver[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := nodeFee(path[i], path[i+1], amtOver[i+1])\n\t\tif fee > lnwire.MaxMilliSatoshi-amtOver[i+1] {\n\t\t\treturn nil, 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(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, e := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\n\t\tif i < n-1 {\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: e.to,\n\t\t\tChannelID: e.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, path, nil\n}\n\nfunc ceilDiv(a lnwire.MilliSatoshi, b int) lnwire.MilliSatoshi {\n\tif b <= 1 {\n\t\treturn a\n\t}\n\n\td := lnwire.MilliSatoshi(b)\n\treturn (a + d - 1) / d\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc reserveRoute(reserved map[edgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey := routeEdgeKey(rt, i)\n\t\treserved[key] += routeAmountAt(rt, i)\n\t}\n}\n\nfunc routeDelivered(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateShardSizes(remaining lnwire.MilliSatoshi,\n\tparts int) []lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tequal := ceilDiv(remaining, parts)\n\traw := []lnwire.MilliSatoshi{\n\t\tequal,\n\t\tequal * 3 / 4,\n\t\tequal * 5 / 4,\n\t\tequal * 3 / 2,\n\t\tequal * 2,\n\t\tremaining,\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tresult := make([]lnwire.MilliSatoshi, 0, len(raw))\n\n\tfor _, shard := range raw {\n\t\tif shard <= 0 {\n\t\t\tshard = 1\n\t\t}\n\t\tif shard > remaining {\n\t\t\tshard = remaining\n\t\t}\n\t\tif seen[shard] {\n\t\t\tcontinue\n\t\t}\n\n\t\tseen[shard] = true\n\t\tresult = append(result, shard)\n\t}\n\n\treturn result\n}\n\ntype planState struct {\n\tremaining lnwire.MilliSatoshi\n\tbudget lnwire.MilliSatoshi\n\troutes []*route.Route\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planRiskPrice(\n\tbudget lnwire.MilliSatoshi) float64 {\n\n\tif budget == lnwire.MaxMilliSatoshi {\n\t\treturn riskPriceMsat\n\t}\n\n\tscale := float64(budget) * 0.55\n\tif scale < 5000 {\n\t\tscale = 5000\n\t}\n\tif scale > riskPriceMsat {\n\t\tscale = riskPriceMsat\n\t}\n\treturn scale\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif total <= 0 || parts <= 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tactualReserved := r.reserved\n\tdefer func() {\n\t\tr.reserved = actualReserved\n\t}()\n\n\tinitialBudget := r.remainingFeeBudget()\n\triskPrice := r.planRiskPrice(initialBudget)\n\n\tfrontier := []*planState{{\n\t\tremaining: total,\n\t\tbudget: initialBudget,\n\t\treserved: cloneReservations(actualReserved),\n\t}}\n\n\tvar complete []*planState\n\n\tfor depth := 0; depth < parts && len(frontier) > 0; depth++ {\n\t\tnextFrontier := make([]*planState, 0, len(frontier)*4)\n\n\t\tfor _, state := range frontier {\n\t\t\tif state.remaining == 0 {\n\t\t\t\tcomplete = append(complete, state)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpartsLeft := parts - depth\n\t\t\tfor _, shard := range candidateShardSizes(\n\t\t\t\tstate.remaining, partsLeft,\n\t\t\t) {\n\t\t\t\tr.reserved = state.reserved\n\n\t\t\t\trt, meta, err := r.findRoute(\n\t\t\t\t\tshard, state.budget,\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\tnextBudget := state.budget\n\t\t\t\tif nextBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\t\tif meta.fee > nextBudget {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tnextBudget -= meta.fee\n\t\t\t\t}\n\n\t\t\t\tnextReserved := cloneReservations(\n\t\t\t\t\tstate.reserved,\n\t\t\t\t)\n\t\t\t\treserveRoute(nextReserved, rt)\n\n\t\t\t\tnextRoutes := make(\n\t\t\t\t\t[]*route.Route, len(state.routes), len(state.routes)+1,\n\t\t\t\t)\n\t\t\t\tcopy(nextRoutes, state.routes)\n\t\t\t\tnextRoutes = append(nextRoutes, rt)\n\n\t\t\t\tnextScore := state.score +\n\t\t\t\t\tfloat64(meta.fee) -\n\t\t\t\t\triskPrice*meta.logProb +\n\t\t\t\t\tfloat64(len(meta.path))*1800 +\n\t\t\t\t\t12000\n\n\t\t\t\tnextFrontier = append(nextFrontier, &planState{\n\t\t\t\t\tremaining: state.remaining - shard,\n\t\t\t\t\tbudget: nextBudget,\n\t\t\t\t\troutes: nextRoutes,\n\t\t\t\t\treserved: nextReserved,\n\t\t\t\t\tscore: nextScore,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(nextFrontier, func(i, j int) bool {\n\t\t\tleft := nextFrontier[i]\n\t\t\tright := nextFrontier[j]\n\n\t\t\tleftEstimate := left.score\n\t\t\trightEstimate := right.score\n\n\t\t\tif left.remaining > 0 {\n\t\t\t\tleftEstimate += 4000\n\t\t\t}\n\t\t\tif right.remaining > 0 {\n\t\t\t\trightEstimate += 4000\n\t\t\t}\n\n\t\t\treturn leftEstimate < rightEstimate\n\t\t})\n\n\t\tif len(nextFrontier) > beamWidth {\n\t\t\tnextFrontier = nextFrontier[:beamWidth]\n\t\t}\n\t\tfrontier = nextFrontier\n\t}\n\n\tfor _, state := range frontier {\n\t\tif state.remaining == 0 {\n\t\t\tcomplete = append(complete, state)\n\t\t}\n\t}\n\n\tif len(complete) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tsort.Slice(complete, func(i, j int) bool {\n\t\treturn complete[i].score < complete[j].score\n\t})\n\n\treturn complete[0].routes, nil\n}\n\nfunc plannedAmount(plan []*route.Route) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, rt := range plan {\n\t\tamt := routeDelivered(rt)\n\t\tif amt > lnwire.MaxMilliSatoshi-total {\n\t\t\treturn lnwire.MaxMilliSatoshi\n\t\t}\n\t\ttotal += amt\n\t}\n\treturn total\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 complete\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\n\tif len(r.plan) > 0 && plannedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tpartsLeft := int(r.spec.MaxParts - inFlightHtlcs)\n\t\tplan, err := r.makePlan(amt, partsLeft)\n\t\tif err != nil && r.failureStreak > 0 {\n\t\t\tplan, err = r.makePlan(amt, partsLeft)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\trt := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.attempts++\n\n\treturn rt, nil\n}\n\nfunc routeAmountAt(rt *route.Route, index int) lnwire.MilliSatoshi {\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc routeEdgeKey(rt *route.Route, index int) edgeKey {\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn edgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tif st := r.state[key]; st != nil {\n\t\tst.belief = b\n\t}\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.beliefs[key] = b\n\tsharedKnowledge.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\te := r.edgeByKey[key]\n\tst := r.state[key]\n\tif e == nil || st == nil {\n\t\treturn\n\t}\n\n\tb := st.belief\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\n\thighEstimate := e.capacity * 88 / 100\n\tif load > highEstimate {\n\t\thighEstimate = load\n\t}\n\tif highEstimate > b.estimate {\n\t\tb.estimate = highEstimate\n\t}\n\tif b.upperFail > 0 && load >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\n\tb.conf = 0.92\n\tb.epoch = r.epoch\n\n\tif st.failures > 0 {\n\t\tst.failures /= 2\n\t}\n\tif st.uses > 0 {\n\t\tst.uses--\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) recordFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\te := r.edgeByKey[key]\n\tst := r.state[key]\n\tif e == nil || st == nil {\n\t\treturn\n\t}\n\n\tst.failures++\n\tst.lastFail = load\n\n\tb := st.belief\n\tif b.upperFail == 0 || load < b.upperFail {\n\t\tb.upperFail = load\n\t}\n\n\tdepletedEstimate := load * 22 / 100\n\tif b.estimate == 0 || depletedEstimate < b.estimate {\n\t\tb.estimate = depletedEstimate\n\t}\n\n\tif b.lowerOK >= load {\n\t\tb.lowerOK = load * 3 / 4\n\t\tb.conf = 0.68\n\t} else {\n\t\tb.conf = 0.90\n\t}\n\n\tif e.from == r.source {\n\t\tb.conf *= 0.8\n\t}\n\n\tb.epoch = r.epoch\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) markAttemptedPath(rt *route.Route,\n\tweight int) {\n\n\tfor i := range rt.Hops {\n\t\tkey := routeEdgeKey(rt, i)\n\t\tst := r.state[key]\n\t\tif st == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tst.uses += weight\n\t\tst.lastFail = routeAmountAt(rt, i) +\n\t\t\tr.reserved[key]\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif rt == nil || r.reported[rt] {\n\t\treturn nil\n\t}\n\tr.reported[rt] = true\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tover := routeAmountAt(rt, i)\n\t\t\tload := over + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, load)\n\t\t\tr.reserved[key] += over\n\t\t}\n\n\t\tdelivered := routeDelivered(rt)\n\t\tif rt.TotalAmount > delivered {\n\t\t\tr.committedFee += rt.TotalAmount - delivered\n\t\t}\n\n\t\tr.failureStreak = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.failureStreak++\n\tr.markAttemptedPath(rt, 1)\n\n\tdesc := fmt.Sprintf(\"%T %v\", result.Failure, result.Failure)\n\tisLiquidity := strings.Contains(\n\t\tdesc, \"TemporaryChannelFailure\",\n\t)\n\tisPolicy := strings.Contains(desc, \"FeeInsufficient\") ||\n\t\tstrings.Contains(desc, \"IncorrectCltvExpiry\")\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(rt.Hops) {\n\t\tkey := routeEdgeKey(rt, failIndex)\n\t\tload := routeAmountAt(rt, failIndex) +\n\t\t\tr.reserved[key]\n\n\t\tswitch {\n\t\tcase isLiquidity:\n\t\t\tr.recordFailure(key, load)\n\n\t\tcase isPolicy:\n\t\t\tif st := r.state[key]; st != nil {\n\t\t\t\tst.failures += 4\n\t\t\t\tst.uses += 2\n\t\t\t\tst.lastFail = load\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif st := r.state[key]; st != nil {\n\t\t\t\tst.failures++\n\t\t\t\tst.lastFail = load\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif isLiquidity {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tst := r.state[key]\n\t\t\tif st == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tst.uses++\n\t\t\tst.lastFail = routeAmountAt(rt, i) +\n\t\t\t\tr.reserved[key]\n\t\t}\n\t} else {\n\t\tr.markAttemptedPath(rt, 2)\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 23,
"parent": 0,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"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\triskPriceMsat = 420000.0\n\thopTimeCost = 12000.0\n\treuseRiskNats = 0.55\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n\tcapacity int64\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\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\tinboundBase int64\n\tinboundRate int64\n}\n\nfunc (e *candidateEdge) key() edgeKey {\n\treturn edgeKey{\n\t\tchanID: e.chanID,\n\t\tfrom: e.from,\n\t\tto: e.to,\n\t\tcapacity: int64(e.capacity),\n\t}\n}\n\nfunc (e *candidateEdge) outboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tfee := int64(e.baseFeeMsat) +\n\t\tint64(amt)*int64(e.feeRatePPM)/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(fee)\n}\n\nfunc (e *candidateEdge) usableShard(\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\n// forwardingFee returns the complete fee charged by the recipient of\n// incoming when it forwards amtOut over outgoing. The inbound component is\n// assessed on the forwarded amount plus the advertised outbound fee, and\n// the combined fee is floored at zero.\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\tamtOut lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tout := int64(outgoing.outboundFee(amtOut))\n\tbasis := int64(amtOut) + out\n\tin := incoming.inboundBase +\n\t\tbasis*incoming.inboundRate/1_000_000\n\n\ttotal := out + in\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype attemptMeta struct {\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n\tobservedLoads []lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[edgeKey]liquidityBelief\n\tdisabled map[edgeKey]bool\n\theld map[edgeKey]lnwire.MilliSatoshi\n\n\tcommittedFees lnwire.MilliSatoshi\n\tplan []plannedRoute\n\tattemptMeta map[*route.Route]attemptMeta\n\tattempts int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\tdisabled: make(map[edgeKey]bool),\n\t\theld: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tattemptMeta: make(map[*route.Route]attemptMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\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\tinboundBase: int64(\n\t\t\t\t\t\tch.InboundFee.BaseFee,\n\t\t\t\t\t),\n\t\t\t\t\tinboundRate: int64(\n\t\t\t\t\t\tch.InboundFee.FeeRate,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\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 _, edges := range r.incomingEdges {\n\t\tfor _, edge := range edges {\n\t\t\tif b, ok := sharedBeliefs.m[edge.key()]; ok {\n\t\t\t\tr.beliefs[edge.key()] = b\n\t\t\t}\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tamt lnwire.MilliSatoshi\n\tscore float64\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *searchQueue) Push(x any) {\n\titem := x.(*searchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\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\n\t// One mode represents a nearly empty direction and decays quickly for\n\t// tiny probes. The other represents a funded direction and falls at\n\t// the public-capacity cliff.\n\tlowMode := 0.5 * math.Exp(-x/0.025)\n\thighMode := 0.5 / (1 + math.Exp((x-0.88)*28))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\treturn 0.995\n\t}\n\n\tp := bimodalPrior(load, edge.capacity)\n\tb := r.beliefs[edge.key()]\n\n\tif b.lowerOK > 0 {\n\t\tif load <= b.lowerOK {\n\t\t\treturn 0.995\n\t\t}\n\n\t\t// A success above the prior's likely range remains useful, but\n\t\t// softens rather than becoming an eternal hard guarantee.\n\t\tratio := float64(b.lowerOK) / float64(load)\n\t\tp = math.Max(p, 0.985*ratio*ratio)\n\t}\n\n\tif b.upperFail > 0 {\n\t\tif load >= b.upperFail {\n\t\t\treturn math.Max(0.012, p*0.03)\n\t\t}\n\n\t\tx := float64(load) / float64(b.upperFail)\n\t\tp *= 1 - 0.85*math.Pow(x, 4)\n\t}\n\n\tif p < 0.012 {\n\t\tp = 0.012\n\t}\n\tif p > 0.995 {\n\t\tp = 0.995\n\t}\n\n\treturn p\n}\n\nfunc addAmount(a, b lnwire.MilliSatoshi) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif b > lnwire.MaxMilliSatoshi-a {\n\t\treturn 0, false\n\t}\n\treturn a + b, true\n}\n\nfunc prependEdge(edge *candidateEdge,\n\tpath []*candidateEdge) []*candidateEdge {\n\n\tresult := make([]*candidateEdge, len(path)+1)\n\tresult[0] = edge\n\tcopy(result[1:], path)\n\treturn result\n}\n\nfunc (r *candidateRouter) findRoute(deliver,\n\tmaxFee lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) (\n\tplannedRoute, error) {\n\n\tif deliver <= 0 {\n\t\treturn plannedRoute{}, errors.New(\"invalid shard amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn plannedRoute{}, errors.New(\"source is target\")\n\t}\n\n\tbest := make(map[route.Vertex]float64)\n\tbest[r.spec.Target] = 0\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tbestScore, ok := best[item.node]\n\t\tif !ok || item.score > bestScore+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, keys, amounts, err := r.buildRoute(\n\t\t\t\tdeliver, item.path,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn plannedRoute{}, err\n\t\t\t}\n\n\t\t\tfee := routeFee(rt)\n\t\t\tif fee > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treturn plannedRoute{\n\t\t\t\trt: rt,\n\t\t\t\tkeys: keys,\n\t\t\t\tamounts: amounts,\n\t\t\t\tscore: item.score,\n\t\t\t}, nil\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tkey := edge.key()\n\t\t\tif r.disabled[key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := deliver\n\t\t\tvar fee lnwire.MilliSatoshi\n\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tif len(item.path) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfee = forwardingFee(\n\t\t\t\t\tedge, item.path[0], item.amt,\n\t\t\t\t)\n\n\t\t\t\tvar valid bool\n\t\t\t\tover, valid = addAmount(item.amt, fee)\n\t\t\t\tif !valid {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !edge.usableShard(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[key]\n\t\t\tload, valid := addAmount(over, reserved)\n\t\t\tif !valid || load > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance := r.localBalances[edge.chanID]\n\t\t\t\tif load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfeesSoFar := over - deliver\n\t\t\tif feesSoFar > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.probability(edge, load)\n\t\t\trisk := -math.Log(p)\n\t\t\tif reserved > 0 {\n\t\t\t\trisk += reuseRiskNats\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) +\n\t\t\t\triskPriceMsat*risk + hopTimeCost\n\n\t\t\tif old, exists := best[edge.from]; exists &&\n\t\t\t\tscore >= old {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[edge.from] = score\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamt: over,\n\t\t\t\tscore: score,\n\t\t\t\tpath: prependEdge(edge, item.path),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn plannedRoute{}, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty path\")\n\t}\n\n\tfor i := 0; i+1 < len(path); i++ {\n\t\tif path[i].to != path[i+1].from {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"disconnected path at hop %d\", i,\n\t\t\t)\n\t\t}\n\t}\n\tif path[0].from != r.source ||\n\t\tpath[len(path)-1].to != r.spec.Target {\n\n\t\treturn nil, nil, nil, errors.New(\"path endpoints mismatch\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tkeys := make([]edgeKey, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := forwardingFee(\n\t\t\tpath[i], path[i+1], amounts[i+1],\n\t\t)\n\n\t\tvar valid bool\n\t\tamounts[i], valid = addAmount(amounts[i+1], fee)\n\t\tif !valid {\n\t\t\treturn nil, nil, nil, errors.New(\n\t\t\t\t\"route amount overflow\",\n\t\t\t)\n\t\t}\n\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tif !edge.usableShard(amounts[i]) {\n\t\t\treturn nil, nil, nil, errors.New(\n\t\t\t\t\"path violates announced htlc limits\",\n\t\t\t)\n\t\t}\n\n\t\tkeys[i] = edge.key()\n\n\t\tamtToForward := deliver\n\t\toutgoingExpiry := finalCltvDelta\n\t\tif i+1 < n {\n\t\t\tamtToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, keys, amounts, nil\n}\n\nfunc routeDeliverAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tdeliver := routeDeliverAmount(rt)\n\tif rt == nil || rt.TotalAmount < deliver {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - deliver\n}\n\nfunc copyReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amount := range src {\n\t\tdst[key] = amount\n\t}\n\treturn dst\n}\n\nfunc proportionalBudget(budget, shard,\n\tremaining lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif budget == lnwire.MaxMilliSatoshi {\n\t\treturn budget\n\t}\n\tif remaining <= 0 || shard >= remaining {\n\t\treturn budget\n\t}\n\n\treturn lnwire.MilliSatoshi(\n\t\tuint64(budget) * uint64(shard) / uint64(remaining),\n\t)\n}\n\nfunc skewedShare(remaining lnwire.MilliSatoshi, partsLeft,\n\tskew int64) lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn remaining\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tshare := lnwire.MilliSatoshi(int64(base) * skew / 100)\n\tif share < 1 {\n\t\tshare = 1\n\t}\n\n\t// Leave a nonzero shard for every later part.\n\tmaxShare := remaining - lnwire.MilliSatoshi(partsLeft-1)\n\tif share > maxShare {\n\t\tshare = maxShare\n\t}\n\n\treturn share\n}\n\nfunc (r *candidateRouter) remainingBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) buildPlan(amount lnwire.MilliSatoshi,\n\tparts int, skew int64) ([]plannedRoute, float64, error) {\n\n\tif parts <= 0 {\n\t\treturn nil, 0, errors.New(\"no parts available\")\n\t}\n\n\treservations := copyReservations(r.held)\n\tremaining := amount\n\tbudget := r.remainingBudget()\n\tplan := make([]plannedRoute, 0, parts)\n\ttotalScore := 0.0\n\n\tfor i := 0; i < parts && remaining > 0; i++ {\n\t\tleft := parts - i\n\t\tshare := skewedShare(remaining, left, skew)\n\t\tshardBudget := proportionalBudget(\n\t\t\tbudget, share, remaining,\n\t\t)\n\n\t\tpr, err := r.findRoute(\n\t\t\tshare, shardBudget, reservations,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\tfee := routeFee(pr.rt)\n\t\tif budget != lnwire.MaxMilliSatoshi {\n\t\t\tif fee > budget {\n\t\t\t\treturn nil, 0, errors.New(\n\t\t\t\t\t\"plan exceeds fee budget\",\n\t\t\t\t)\n\t\t\t}\n\t\t\tbudget -= fee\n\t\t}\n\n\t\tfor j, key := range pr.keys {\n\t\t\tnext, valid := addAmount(\n\t\t\t\treservations[key], pr.amounts[j],\n\t\t\t)\n\t\t\tif !valid {\n\t\t\t\treturn nil, 0, errors.New(\n\t\t\t\t\t\"reservation overflow\",\n\t\t\t\t)\n\t\t\t}\n\t\t\treservations[key] = next\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score + 60000\n\t\tremaining -= share\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, 0, errors.New(\"incomplete shard plan\")\n\t}\n\n\treturn plan, totalScore, nil\n}\n\nfunc (r *candidateRouter) planRoutes(amount lnwire.MilliSatoshi,\n\tslots int) error {\n\n\tvar (\n\t\tbestPlan []plannedRoute\n\t\tbestScore = math.Inf(1)\n\t)\n\n\tskews := []int64{100, 135, 70}\n\n\tfor parts := 1; parts <= slots; parts++ {\n\t\tfor _, skew := range skews {\n\t\t\tif parts == 1 && skew != 100 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tplan, score, err := r.buildPlan(\n\t\t\t\tamount, parts, skew,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif score < bestScore {\n\t\t\t\tbestScore = score\n\t\t\t\tbestPlan = plan\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(bestPlan) == 0 {\n\t\treturn errors.New(\"no route found\")\n\t}\n\n\tr.plan = bestPlan\n\treturn nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tmaxAttempts := 12 + int(r.spec.MaxParts)*4\n\tif maxAttempts > 48 {\n\t\tmaxAttempts = 48\n\t}\n\tif maxAttempts < 16 {\n\t\tmaxAttempts = 16\n\t}\n\tif r.attempts >= maxAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\n\tmaxParts := int(r.spec.MaxParts)\n\tif maxParts <= 0 {\n\t\tmaxParts = 1\n\t}\n\tif int(inFlightHtlcs) >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tfor len(r.plan) != 0 {\n\t\tpr := r.plan[0]\n\t\tdeliver := routeDeliverAmount(pr.rt)\n\t\tif deliver <= 0 || deliver > amt ||\n\t\t\trouteFee(pr.rt) > r.remainingBudget() {\n\n\t\t\tr.plan = nil\n\t\t\tbreak\n\t\t}\n\n\t\tr.plan = r.plan[1:]\n\n\t\tmeta := attemptMeta{\n\t\t\tkeys: append([]edgeKey(nil), pr.keys...),\n\t\t\tamounts: append([]lnwire.MilliSatoshi(nil), pr.amounts...),\n\t\t\tobservedLoads: make(\n\t\t\t\t[]lnwire.MilliSatoshi, len(pr.keys),\n\t\t\t),\n\t\t}\n\t\tfor i, key := range pr.keys {\n\t\t\tload, valid := addAmount(\n\t\t\t\tr.held[key], pr.amounts[i],\n\t\t\t)\n\t\t\tif !valid {\n\t\t\t\tr.plan = nil\n\t\t\t\treturn nil, errors.New(\n\t\t\t\t\t\"attempt load overflow\",\n\t\t\t\t)\n\t\t\t}\n\t\t\tmeta.observedLoads[i] = load\n\t\t}\n\n\t\tr.attemptMeta[pr.rt] = meta\n\t\tr.attempts++\n\t\treturn pr.rt, nil\n\t}\n\n\tslots := maxParts - int(inFlightHtlcs)\n\tif err := r.planRoutes(amt, slots); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r.RequestRoute(amt, inFlightHtlcs)\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(\n\tkey edgeKey, load lnwire.MilliSatoshi) {\n\n\tb := r.beliefs[key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\n\t// A proven success at or above an old failure bound demonstrates that\n\t// the old observation was stale or caused by temporary reservations.\n\tif b.upperFail != 0 && load >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\n\tr.storeBelief(key, b)\n}\n\nfunc (r *candidateRouter) recordFailure(\n\tkey edgeKey, load lnwire.MilliSatoshi) {\n\n\tb := r.beliefs[key]\n\tif b.upperFail == 0 || load < b.upperFail {\n\t\tb.upperFail = load\n\t}\n\n\tif b.lowerOK >= b.upperFail {\n\t\tb.lowerOK = b.upperFail * 3 / 4\n\t}\n\n\tr.storeBelief(key, b)\n}\n\nfunc failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tmeta, haveMeta := r.attemptMeta[rt]\n\tdelete(r.attemptMeta, rt)\n\n\tif result.Failure == nil {\n\t\tfee := routeFee(rt)\n\t\tif next, valid := addAmount(r.committedFees, fee); valid {\n\t\t\tr.committedFees = next\n\t\t}\n\n\t\tif haveMeta {\n\t\t\tfor i, key := range meta.keys {\n\t\t\t\tnext, valid := addAmount(\n\t\t\t\t\tr.held[key], meta.amounts[i],\n\t\t\t\t)\n\t\t\t\tif valid {\n\t\t\t\t\tr.held[key] = next\n\t\t\t\t}\n\t\t\t\tr.recordSuccess(\n\t\t\t\t\tkey, meta.observedLoads[i],\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Any failed member invalidates the remainder of an up-front plan.\n\t// Replanning incorporates the new evidence while preserving held\n\t// reservations from siblings that already succeeded.\n\tr.plan = nil\n\n\tif !haveMeta {\n\t\treturn nil\n\t}\n\n\tfailIdx := failureIndex(rt, result.FailureSource)\n\tcode := result.Failure.Code()\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif failIdx < 0 || failIdx >= len(meta.keys) {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Every channel before the failing outgoing channel demonstrably\n\t\t// carried its total concurrent load.\n\t\tfor i := 0; i < failIdx; i++ {\n\t\t\tr.recordSuccess(\n\t\t\t\tmeta.keys[i], meta.observedLoads[i],\n\t\t\t)\n\t\t}\n\t\tr.recordFailure(\n\t\t\tmeta.keys[failIdx],\n\t\t\tmeta.observedLoads[failIdx],\n\t\t)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tif failIdx >= 0 && failIdx < len(meta.keys) {\n\t\t\tr.disabled[meta.keys[failIdx]] = true\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 24,
"parent": 4,
"score": 0.1672,
"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\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\n\thopCostMsat = float64(12_000)\n\treusedEdgeCostMsat = float64(1_250_000)\n\textraPartCostMsat = float64(55_000)\n\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 10\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 48\n\tmaxAllocationPlans = 40\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) * 0.55\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\n\treturn price\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tvar cutoff lnwire.MilliSatoshi\n\tswitch {\n\tcase r.localFails[key] >= 3:\n\t\tcutoff = upper * 35 / 100\n\tcase r.localFails[key] == 2:\n\t\tcutoff = upper / 2\n\tdefault:\n\t\tcutoff = upper * 65 / 100\n\t}\n\n\tif cutoff <= 0 {\n\t\tcutoff = 1\n\t}\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.995\n\t\t\t\tif r.localUpper[edge.key] > 0 {\n\t\t\t\t\tp = 0.72\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.55\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := total / lnwire.MilliSatoshi(sum) *\n\t\t\tlnwire.MilliSatoshi(weight)\n\t\tpart += (total % lnwire.MilliSatoshi(sum)) *\n\t\t\tlnwire.MilliSatoshi(weight) /\n\t\t\tlnwire.MilliSatoshi(sum)\n\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tminRest := lnwire.MilliSatoshi(len(weights) - i - 1)\n\t\tif part > remaining-minRest {\n\t\t\tpart = remaining - minRest\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tsum -= weight\n\t\ttotal = remaining\n\t}\n\n\treturn result\n}\n\nfunc equalAllocation(total lnwire.MilliSatoshi,\n\tparts int) []lnwire.MilliSatoshi {\n\n\tweights := make([]int64, parts)\n\tfor i := range weights {\n\t\tweights[i] = 1\n\t}\n\treturn weightedAllocation(total, weights)\n}\n\nfunc sameAllocation(a, b []lnwire.MilliSatoshi) 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 appendAllocation(dst *[][]lnwire.MilliSatoshi,\n\tallocation []lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi) {\n\n\tif len(allocation) == 0 {\n\t\treturn\n\t}\n\n\tvar sum lnwire.MilliSatoshi\n\tfor _, amt := range allocation {\n\t\tif amt <= 0 {\n\t\t\treturn\n\t\t}\n\t\tsum += amt\n\t}\n\tif sum != total {\n\t\treturn\n\t}\n\n\tfor _, old := range *dst {\n\t\tif sameAllocation(old, allocation) {\n\t\t\treturn\n\t\t}\n\t}\n\tif len(*dst) < maxAllocationPlans {\n\t\t*dst = append(*dst, allocation)\n\t}\n}\n\nfunc allocationWithFirst(total, first lnwire.MilliSatoshi,\n\tparts int) []lnwire.MilliSatoshi {\n\n\tif parts < 2 || first <= 0 ||\n\t\tfirst > total-lnwire.MilliSatoshi(parts-1) {\n\n\t\treturn nil\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, 0, parts)\n\tresult = append(result, first)\n\tresult = append(\n\t\tresult, equalAllocation(total-first, parts-1)...,\n\t)\n\treturn result\n}\n\nfunc reverseAllocation(src []lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\tdst := make([]lnwire.MilliSatoshi, len(src))\n\tfor i := range src {\n\t\tdst[len(src)-1-i] = src[i]\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) planningBoundaries(\n\ttotal lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tbounds := make([]lnwire.MilliSatoshi, 0)\n\n\tadd := func(v lnwire.MilliSatoshi) {\n\t\tif v <= 0 || v >= total {\n\t\t\treturn\n\t\t}\n\t\tfor _, old := range bounds {\n\t\t\tdiff := old - v\n\t\t\tif diff < 0 {\n\t\t\t\tdiff = -diff\n\t\t\t}\n\t\t\tif diff <= v/20 {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbounds = append(bounds, v)\n\t}\n\n\tfor key := range r.localUpper {\n\t\tcutoff := r.retryCutoff(key)\n\t\tif cutoff > r.reserved[key] {\n\t\t\tadd((cutoff - r.reserved[key]) * 9 / 10)\n\t\t}\n\t}\n\n\tfor key, edge := range r.edgeLookup {\n\t\tif edge.from != r.source {\n\t\t\tcontinue\n\t\t}\n\n\t\tlimit := edge.capacity\n\t\tif edge.maxHTLC > 0 && edge.maxHTLC < limit {\n\t\t\tlimit = edge.maxHTLC\n\t\t}\n\t\tif balance, ok := r.localBalances[edge.chanID];\n\t\t\tok && balance < limit {\n\n\t\t\tlimit = balance\n\t\t}\n\t\tif cutoff := r.retryCutoff(key);\n\t\t\tcutoff > 0 && cutoff < limit {\n\n\t\t\tlimit = cutoff\n\t\t}\n\t\tif limit > r.reserved[key] {\n\t\t\tadd((limit - r.reserved[key]) * 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) > 12 {\n\t\tbounds = bounds[:12]\n\t}\n\treturn bounds\n}\n\nfunc (r *candidateRouter) allocationPatterns(\n\ttotal lnwire.MilliSatoshi, parts int) [][]lnwire.MilliSatoshi {\n\n\tvar patterns [][]lnwire.MilliSatoshi\n\tappendAllocation(&patterns, equalAllocation(total, parts), total)\n\n\tvar weightSets [][]int64\n\tswitch parts {\n\tcase 2:\n\t\tweightSets = [][]int64{\n\t\t\t{40, 60}, {60, 40},\n\t\t\t{33, 67}, {67, 33},\n\t\t\t{25, 75}, {75, 25},\n\t\t\t{15, 85}, {85, 15},\n\t\t}\n\n\tcase 3:\n\t\tweightSets = [][]int64{\n\t\t\t{20, 30, 50}, {50, 30, 20},\n\t\t\t{15, 35, 50}, {50, 35, 15},\n\t\t\t{20, 40, 40}, {40, 40, 20},\n\t\t}\n\n\tcase 4:\n\t\tweightSets = [][]int64{\n\t\t\t{15, 20, 25, 40},\n\t\t\t{40, 25, 20, 15},\n\t\t\t{10, 20, 30, 40},\n\t\t\t{40, 30, 20, 10},\n\t\t}\n\n\tdefault:\n\t\tif parts > 4 {\n\t\t\tascending := make([]int64, parts)\n\t\t\tdescending := make([]int64, parts)\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tascending[i] = int64(i + 1)\n\t\t\t\tdescending[i] = int64(parts - i)\n\t\t\t}\n\t\t\tweightSets = append(weightSets, ascending, descending)\n\t\t}\n\t}\n\n\tfor _, weights := range weightSets {\n\t\tappendAllocation(\n\t\t\t&patterns, weightedAllocation(total, weights), total,\n\t\t)\n\t}\n\n\tif parts > 1 {\n\t\tfor _, boundary := range r.planningBoundaries(total) {\n\t\t\tallocation := allocationWithFirst(\n\t\t\t\ttotal, boundary, parts,\n\t\t\t)\n\t\t\tif allocation == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tappendAllocation(&patterns, allocation, total)\n\t\t\tappendAllocation(\n\t\t\t\t&patterns, reverseAllocation(allocation), total,\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tvar remainingAmount lnwire.MilliSatoshi\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\tvar totalScore float64\n\n\tfor _, shard := range allocation {\n\t\tshardCap := remainingBudget\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\tremainingAmount > shard {\n\n\t\t\tshardCap = remainingBudget * shard / remainingAmount\n\t\t\tallowance := remainingBudget * 15 / 100\n\t\t\tif allowance > 100_000 {\n\t\t\t\tallowance = 100_000\n\t\t\t}\n\t\t\tif shardCap <= remainingBudget-allowance {\n\t\t\t\tshardCap += allowance\n\t\t\t} else {\n\t\t\t\tshardCap = remainingBudget\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\treturn plan, totalScore, true\n}\n\nfunc appendAmount(dst *[]lnwire.MilliSatoshi,\n\tamt, total lnwire.MilliSatoshi) {\n\n\tif amt <= 0 || amt >= total {\n\t\treturn\n\t}\n\tfor _, old := range *dst {\n\t\tdiff := old - amt\n\t\tif diff < 0 {\n\t\t\tdiff = -diff\n\t\t}\n\t\tif diff <= amt/50 {\n\t\t\treturn\n\t\t}\n\t}\n\t*dst = append(*dst, amt)\n}\n\nfunc (r *candidateRouter) partialCandidates(\n\ttotal lnwire.MilliSatoshi, slots uint32) []lnwire.MilliSatoshi {\n\n\tvar candidates []lnwire.MilliSatoshi\n\n\tfor _, pair := range [][2]int64{\n\t\t{7, 8}, {3, 4}, {2, 3}, {1, 2},\n\t\t{2, 5}, {1, 3}, {1, 4}, {1, 6},\n\t\t{1, 8}, {1, 12}, {1, 16},\n\t} {\n\t\tappendAmount(\n\t\t\t&candidates,\n\t\t\ttotal*lnwire.MilliSatoshi(pair[0])/\n\t\t\t\tlnwire.MilliSatoshi(pair[1]),\n\t\t\ttotal,\n\t\t)\n\t}\n\n\tif slots > 1 {\n\t\tappendAmount(\n\t\t\t&candidates,\n\t\t\t(total+lnwire.MilliSatoshi(slots)-1)/\n\t\t\t\tlnwire.MilliSatoshi(slots),\n\t\t\ttotal,\n\t\t)\n\t}\n\n\tfor _, boundary := range r.planningBoundaries(total) {\n\t\tappendAmount(&candidates, boundary, total)\n\t}\n\n\tsort.Slice(candidates, func(i, j int) bool {\n\t\treturn candidates[i] > candidates[j]\n\t})\n\treturn candidates\n}\n\nfunc (r *candidateRouter) makePartialPlan(total lnwire.MilliSatoshi,\n\tslots uint32, feeBudget lnwire.MilliSatoshi) ([]*plannedRoute, error) {\n\n\tcandidates := r.partialCandidates(total, slots)\n\tfor _, shard := range candidates {\n\t\tcap := feeBudget\n\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\tcap = feeBudget * shard / total\n\t\t\tallowance := feeBudget * 15 / 100\n\t\t\tif allowance > 100_000 {\n\t\t\t\tallowance = 100_000\n\t\t\t}\n\t\t\tif cap <= feeBudget-allowance {\n\t\t\t\tcap += allowance\n\t\t\t} else {\n\t\t\t\tcap = feeBudget\n\t\t\t}\n\t\t\tif cap <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\tif err == nil {\n\t\t\treturn []*plannedRoute{pr}, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tif total < lnwire.MilliSatoshi(parts) {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, allocation := range r.allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\treturn r.makePartialPlan(total, slots, feeBudget)\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.25\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\tif failIdx < 0 &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure &&\n\t\tlen(meta.path) > 0 {\n\n\t\tfailIdx = 0\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t\tr.edgePenalty[meta.path[i].key] *= 0.85\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t850_000 + float64(count)*350_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 10_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_500_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 750_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 25,
"parent": 0,
"score": 0.1296,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskPriceMsat = 420000.0\n\tmaxNodeLabels = 7\n\tminShardMsat = lnwire.MilliSatoshi(1000)\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\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\t// The inbound fee belongs to the receiving node and is charged when\n\t// that node forwards through its next outgoing channel.\n\tinboundBase int64\n\tinboundRate int64\n}\n\nfunc (e *candidateEdge) outboundFee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif outgoing == nil {\n\t\treturn 0\n\t}\n\n\toutFee := outgoing.outboundFee(amt)\n\tinBase := int64(amt) + int64(outFee)\n\tinFee := incoming.inboundBase +\n\t\tinBase*incoming.inboundRate/1_000_000\n\n\ttotal := int64(outFee) + inFee\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tokCount uint32\n\tfailCount uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.RWMutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype localFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype activeRoute struct {\n\trt *route.Route\n\tfee lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tlocalFailures map[edgeKey]localFailure\n\tpolicyBad map[edgeKey]bool\n\n\tactive []activeRoute\n\tplanned []*route.Route\n\n\tcommittedFees 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: localBalances,\n\t\tlocalFailures: make(map[edgeKey]localFailure),\n\t\tpolicyBad: make(map[edgeKey]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\te := &candidateEdge{\n\t\t\t\t\tkey: edgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.\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\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], e,\n\t\t\t\t)\n\t\t\t\tr.edges[e.key] = e\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\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.5 * math.Exp(-x/0.055)\n\thighMode := 0.5 / (1 + math.Exp((x-0.94)/0.025))\n\tp := lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tp := bimodalPrior(load, e.capacity)\n\n\tsharedBeliefs.RLock()\n\tb, ok := sharedBeliefs.m[e.key]\n\tsharedBeliefs.RUnlock()\n\tif !ok {\n\t\treturn p\n\t}\n\n\tif b.lowerOK > 0 && load <= b.lowerOK {\n\t\tconf := 1 - math.Exp(-float64(b.okCount)/2)\n\t\tp = p*(1-conf) + 0.995*conf\n\t}\n\n\tif b.upperFail > 0 {\n\t\tconf := 1 - math.Exp(-float64(b.failCount)/2)\n\t\tswitch {\n\t\tcase load >= b.upperFail:\n\t\t\tsoftCeiling := 0.012\n\t\t\tp = p*(1-conf) + softCeiling*conf\n\n\t\tcase b.lowerOK > 0 && b.upperFail > b.lowerOK &&\n\t\t\tload > b.lowerOK:\n\n\t\t\tspan := float64(b.upperFail - b.lowerOK)\n\t\t\troom := float64(b.upperFail-load) / span\n\t\t\tinformed := 0.012 + 0.983*room\n\t\t\tp = p*(1-conf) + informed*conf\n\t\t}\n\t}\n\n\tif b.estimate > 0 {\n\t\tscale := float64(e.capacity) * 0.08\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t(float64(load)-float64(b.estimate))/scale,\n\t\t))\n\t\tconf := 1 - math.Exp(\n\t\t\t-float64(b.okCount+b.failCount)/4,\n\t\t)\n\t\tp = p*(1-0.45*conf) + estimateP*0.45*conf\n\t}\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc recordSuccess(key edgeKey, load lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tb := sharedBeliefs.m[key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tif b.estimate < load {\n\t\tb.estimate = load\n\t}\n\tb.okCount++\n\tif b.upperFail > 0 && b.lowerOK >= b.upperFail {\n\t\tb.upperFail = 0\n\t\tb.failCount /= 2\n\t}\n\tsharedBeliefs.m[key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc recordFailure(key edgeKey, load lnwire.MilliSatoshi) {\n\tsharedBeliefs.Lock()\n\tb := sharedBeliefs.m[key]\n\tif b.upperFail == 0 || load < b.upperFail {\n\t\tb.upperFail = load\n\t}\n\tif b.estimate == 0 || load < b.estimate {\n\t\tb.estimate = load\n\t}\n\tb.failCount++\n\tif b.lowerOK >= b.upperFail {\n\t\tb.lowerOK = 0\n\t\tb.okCount /= 2\n\t}\n\tsharedBeliefs.m[key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc routeDelivered(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeEdges(rt *route.Route) []edgeKey {\n\tkeys := make([]edgeKey, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\tfor _, hop := range rt.Hops {\n\t\tkeys = append(keys, edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\treturn keys\n}\n\nfunc routeAmounts(rt *route.Route) []lnwire.MilliSatoshi {\n\tamounts := make([]lnwire.MilliSatoshi, len(rt.Hops))\n\tfor i := range rt.Hops {\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}\n\treturn amounts\n}\n\nfunc cloneReservations(src map[edgeKey]lnwire.MilliSatoshi) map[\n\tedgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc addRouteReservations(res map[edgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tkeys := routeEdges(rt)\n\tamounts := routeAmounts(rt)\n\tfor i, key := range keys {\n\t\tres[key] += amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) reservations() map[\n\tedgeKey]lnwire.MilliSatoshi {\n\n\tres := make(map[edgeKey]lnwire.MilliSatoshi)\n\tfor _, active := range r.active {\n\t\taddRouteReservations(res, active.rt)\n\t}\n\treturn res\n}\n\nfunc (r *candidateRouter) budgetRemaining() (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi, false\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0, true\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees, true\n}\n\ntype pathItem struct {\n\tnode route.Vertex\n\tneed lnwire.MilliSatoshi\n\tfees lnwire.MilliSatoshi\n\tscore float64\n\tout *candidateEdge\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype pathQueue []*pathItem\n\nfunc (q pathQueue) Len() int { return len(q) }\nfunc (q pathQueue) Less(i, j int) bool {\n\tif q[i].score == q[j].score {\n\t\treturn q[i].need < q[j].need\n\t}\n\treturn q[i].score < q[j].score\n}\nfunc (q pathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\nfunc (q *pathQueue) Push(x any) {\n\titem := x.(*pathItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *pathQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype nodeLabel struct {\n\tneed lnwire.MilliSatoshi\n\tfees lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc acceptLabel(labels map[route.Vertex][]nodeLabel,\n\tnode route.Vertex, label nodeLabel) bool {\n\n\tcurrent := labels[node]\n\tfor _, old := range current {\n\t\tif old.need <= label.need &&\n\t\t\told.fees <= label.fees &&\n\t\t\told.score <= label.score {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfiltered := current[:0]\n\tfor _, old := range current {\n\t\tif label.need <= old.need &&\n\t\t\tlabel.fees <= old.fees &&\n\t\t\tlabel.score <= old.score {\n\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, old)\n\t}\n\tfiltered = append(filtered, label)\n\n\tif len(filtered) > maxNodeLabels {\n\t\tworst := 0\n\t\tfor i := 1; i < len(filtered); i++ {\n\t\t\tif filtered[i].score > filtered[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\t\tif worst == len(filtered)-1 {\n\t\t\tlabels[node] = filtered[:len(filtered)-1]\n\t\t\treturn false\n\t\t}\n\t\tfiltered[worst] = filtered[len(filtered)-1]\n\t\tfiltered = filtered[:len(filtered)-1]\n\t}\n\n\tlabels[node] = filtered\n\treturn true\n}\n\nfunc pathContains(path []*candidateEdge, node route.Vertex) bool {\n\tfor _, e := range path {\n\t\tif e.key.from == node || e.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc prependEdge(e *candidateEdge,\n\tpath []*candidateEdge) []*candidateEdge {\n\n\tnext := make([]*candidateEdge, len(path)+1)\n\tnext[0] = e\n\tcopy(next[1:], path)\n\treturn next\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi,\n\treserved lnwire.MilliSatoshi) float64 {\n\n\tif e.key.from == r.source {\n\t\tp := 0.985\n\t\tif reserved > 0 {\n\t\t\tp *= 0.92\n\t\t}\n\t\treturn p\n\t}\n\n\tp := beliefProbability(e, load)\n\tif failure, ok := r.localFailures[e.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif load >= failure.upper {\n\t\t\tp *= math.Pow(0.25, float64(failure.count))\n\t\t} else if load*4 >= failure.upper*3 {\n\t\t\tp *= math.Pow(0.55, float64(failure.count))\n\t\t}\n\t}\n\n\tif reserved > 0 {\n\t\t// This is both a load adjustment and a corridor-reuse surcharge.\n\t\tp *= 0.42\n\t}\n\tif p < 0.0002 {\n\t\treturn 0.0002\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi, feeBounded bool) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tpq := &pathQueue{}\n\theap.Push(pq, &pathItem{\n\t\tnode: r.spec.Target,\n\t\tneed: amt,\n\t})\n\n\tlabels := make(map[route.Vertex][]nodeLabel)\n\tlabels[r.spec.Target] = []nodeLabel{{\n\t\tneed: amt,\n\t}}\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*pathItem)\n\t\tif item.node == r.source {\n\t\t\treturn r.buildRoute(amt, item.path)\n\t\t}\n\n\t\tfor _, incoming := range r.incomingEdges[item.node] {\n\t\t\tif r.policyBad[incoming.key] ||\n\t\t\t\tpathContains(item.path, incoming.key.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnodeFee := forwardingFee(\n\t\t\t\tincoming, item.out, item.need,\n\t\t\t)\n\t\t\tover := item.need + nodeFee\n\t\t\tif !incoming.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[incoming.key]\n\t\t\tload := over + reserved\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif incoming.key.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[\n\t\t\t\t\tincoming.key.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfees := item.fees + nodeFee\n\t\t\tif feeBounded && fees > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(incoming, load, reserved)\n\t\t\tscore := item.score + float64(nodeFee) +\n\t\t\t\triskPriceMsat*(-math.Log(p)) + 12000\n\n\t\t\tlabel := nodeLabel{\n\t\t\t\tneed: over,\n\t\t\t\tfees: fees,\n\t\t\t\tscore: score,\n\t\t\t}\n\t\t\tif !acceptLabel(labels, incoming.key.from, label) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\theap.Push(pq, &pathItem{\n\t\t\t\tnode: incoming.key.from,\n\t\t\t\tneed: over,\n\t\t\t\tfees: fees,\n\t\t\t\tscore: score,\n\t\t\t\tout: incoming,\n\t\t\t\tpath: prependEdge(\n\t\t\t\t\tincoming, item.path,\n\t\t\t\t),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := forwardingFee(\n\t\t\tpath[i], path[i+1], amounts[i+1],\n\t\t)\n\t\tamounts[i] = amounts[i+1] + fee\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, e := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < n {\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: e.key.to,\n\t\t\tChannelID: e.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\trt := &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}\n\n\tif !r.validateRoutePolicies(rt, path) {\n\t\treturn nil, errors.New(\"constructed route violates policy\")\n\t}\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) validateRoutePolicies(rt *route.Route,\n\tpath []*candidateEdge) bool {\n\n\tamounts := routeAmounts(rt)\n\tif len(amounts) != len(path) {\n\t\treturn false\n\t}\n\tfor i, e := range path {\n\t\tif !e.usableHTLC(amounts[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc proportionalBudget(total, shard,\n\tremaining lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif remaining <= 0 || shard >= remaining {\n\t\treturn total\n\t}\n\n\tshare := lnwire.MilliSatoshi(\n\t\tint64(total) * int64(shard) / int64(remaining),\n\t)\n\t// Give each shard a little flexibility for base fees while retaining\n\t// most of the budget for the rest of the planned set.\n\tslack := total / 20\n\tif share+slack < share || share+slack > total {\n\t\treturn total\n\t}\n\treturn share + slack\n}\n\nfunc (r *candidateRouter) routeAtAmount(\n\tamt, remaining lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tbudget lnwire.MilliSatoshi, bounded bool) (*route.Route, error) {\n\n\tmaxFee := budget\n\tif bounded {\n\t\tmaxFee = proportionalBudget(budget, amt, remaining)\n\t}\n\treturn r.findRoute(\n\t\tamt, reservations, maxFee, bounded,\n\t)\n}\n\nfunc (r *candidateRouter) largestShard(\n\tremaining lnwire.MilliSatoshi, slots uint32,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tbudget lnwire.MilliSatoshi, bounded bool) (*route.Route, error) {\n\n\tif rt, err := r.findRoute(\n\t\tremaining, reservations, budget, bounded,\n\t); err == nil {\n\t\treturn rt, nil\n\t}\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"no parts available\")\n\t}\n\n\taverage := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\tif average < minShardMsat {\n\t\taverage = minShardMsat\n\t}\n\n\tvar base *route.Route\n\tprobes := []lnwire.MilliSatoshi{\n\t\taverage,\n\t\taverage * 2,\n\t\taverage * 3 / 2,\n\t\taverage / 2,\n\t\taverage / 3,\n\t\taverage / 4,\n\t}\n\n\tfor _, probe := range probes {\n\t\tif probe < minShardMsat {\n\t\t\tprobe = minShardMsat\n\t\t}\n\t\tif probe > remaining {\n\t\t\tprobe = remaining\n\t\t}\n\t\trt, err := r.routeAtAmount(\n\t\t\tprobe, remaining, reservations, budget, bounded,\n\t\t)\n\t\tif err == nil {\n\t\t\tif base == nil ||\n\t\t\t\trouteDelivered(rt) > routeDelivered(base) {\n\n\t\t\t\tbase = rt\n\t\t\t}\n\t\t}\n\t}\n\n\tif base == nil {\n\t\treturn nil, errors.New(\"no shard route found\")\n\t}\n\n\tlow := routeDelivered(base)\n\thigh := remaining\n\tbest := base\n\n\t// Public maxima and capacity make feasibility nearly monotonic. A\n\t// short binary search finds unequal corridor-sized shards without\n\t// spending wire attempts probing those boundaries.\n\tfor i := 0; i < 8 && high-low > minShardMsat; i++ {\n\t\tmid := low + (high-low+1)/2\n\t\trt, err := r.routeAtAmount(\n\t\t\tmid, remaining, reservations, budget, bounded,\n\t\t)\n\t\tif err != nil {\n\t\t\thigh = mid - 1\n\t\t\tcontinue\n\t\t}\n\t\tbest = rt\n\t\tlow = mid\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tbudget, bounded := r.budgetRemaining()\n\tif bounded && budget <= 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\treservations := cloneReservations(r.reservations())\n\tremaining := amt\n\tplan := make([]*route.Route, 0, parts)\n\n\tfor slots := parts; slots > 0 && remaining > 0; slots-- {\n\t\trt, err := r.largestShard(\n\t\t\tremaining, slots, reservations, budget, bounded,\n\t\t)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tdelivered := routeDelivered(rt)\n\t\tif delivered <= 0 || delivered > remaining {\n\t\t\tbreak\n\t\t}\n\n\t\tfee := routeFee(rt)\n\t\tif bounded {\n\t\t\tif fee > budget {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tbudget -= fee\n\t\t}\n\n\t\tplan = append(plan, rt)\n\t\taddRouteReservations(reservations, rt)\n\t\tremaining -= delivered\n\t}\n\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn plan, 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 inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif len(r.planned) != 0 {\n\t\trt := r.planned[0]\n\t\tif routeDelivered(rt) <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tfee := routeFee(rt)\n\t\t\tr.active = append(r.active, activeRoute{\n\t\t\t\trt: rt,\n\t\t\t\tfee: fee,\n\t\t\t})\n\t\t\tr.committedFees += fee\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tparts := r.spec.MaxParts - inFlightHtlcs\n\tplan, err := r.makePlan(amt, parts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.planned = plan\n\n\trt := r.planned[0]\n\tr.planned = r.planned[1:]\n\tfee := routeFee(rt)\n\tr.active = append(r.active, activeRoute{\n\t\trt: rt,\n\t\tfee: fee,\n\t})\n\tr.committedFees += fee\n\tr.attempts++\n\n\treturn rt, nil\n}\n\nfunc sameRoute(a, b *route.Route) bool {\n\tif a == b {\n\t\treturn true\n\t}\n\tif a == nil || b == nil ||\n\t\ta.TotalAmount != b.TotalAmount ||\n\t\tlen(a.Hops) != len(b.Hops) {\n\n\t\treturn false\n\t}\n\tfor i := range a.Hops {\n\t\tif a.Hops[i].ChannelID != b.Hops[i].ChannelID ||\n\t\t\ta.Hops[i].PubKeyBytes != b.Hops[i].PubKeyBytes ||\n\t\t\ta.Hops[i].AmtToForward !=\n\t\t\t\tb.Hops[i].AmtToForward {\n\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) removeFailedRoute(rt *route.Route) {\n\tfor i, active := range r.active {\n\t\tif !sameRoute(active.rt, rt) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif r.committedFees >= active.fee {\n\t\t\tr.committedFees -= active.fee\n\t\t} else {\n\t\t\tr.committedFees = 0\n\t\t}\n\t\tcopy(r.active[i:], r.active[i+1:])\n\t\tr.active = r.active[:len(r.active)-1]\n\t\treturn\n\t}\n}\n\nfunc failureChannelIndex(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) observedLoads(\n\trt *route.Route) []lnwire.MilliSatoshi {\n\n\tkeys := routeEdges(rt)\n\tamounts := routeAmounts(rt)\n\treserved := r.reservations()\n\tloads := make([]lnwire.MilliSatoshi, len(keys))\n\n\tfor i, key := range keys {\n\t\tloads[i] = reserved[key]\n\t\tif loads[i] < amounts[i] {\n\t\t\tloads[i] = amounts[i]\n\t\t}\n\t}\n\treturn loads\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tif rt == nil {\n\t\treturn nil\n\t}\n\n\tkeys := routeEdges(rt)\n\tloads := r.observedLoads(rt)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\trecordSuccess(key, loads[i])\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.removeFailedRoute(rt)\n\tr.planned = nil\n\n\tfailIdx := failureChannelIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIdx < 0 {\n\t\t// Dispatcher-side refusals, including fee-budget rejection, reveal\n\t\t// no liquidity information.\n\t\treturn nil\n\t}\n\n\t// Every channel preceding the failing node successfully carried the\n\t// total load imposed by all of our currently held shards.\n\tpassed := failIdx\n\tif passed > len(keys) {\n\t\tpassed = len(keys)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\trecordSuccess(keys[i], loads[i])\n\t}\n\n\tif failIdx >= len(keys) {\n\t\treturn nil\n\t}\n\n\tcode := result.Failure.Code()\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tkey := keys[failIdx]\n\t\tload := loads[failIdx]\n\t\trecordFailure(key, load)\n\n\t\tlocal := r.localFailures[key]\n\t\tif local.upper == 0 || load < local.upper {\n\t\t\tlocal.upper = load\n\t\t}\n\t\tlocal.count++\n\t\tr.localFailures[key] = local\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// These are deterministic policy failures. Avoid repeating the\n\t\t// same invalid directed edge while retaining liquidity evidence\n\t\t// learned from the hops that preceded it.\n\t\tr.policyBad[keys[failIdx]] = true\n\t}\n\n\treturn nil\n}\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\n\t\t\"bimodal-budget-router(source=%v,target=%v)\",\n\t\tr.source, r.spec.Target,\n\t)\n}"
}
},
{
"id": 26,
"parent": 0,
"score": 0.3656,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxSearchHops = 40\n\tmaxRouterTries = 64\n)\n\ntype edgeKey struct {\n\tscope route.Vertex\n\tfrom route.Vertex\n\tto route.Vertex\n\tchanID uint64\n\tcapacity lnwire.MilliSatoshi\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tfee := int64(e.baseFeeMsat) +\n\t\tint64(amt)*int64(e.feeRatePPM)/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(fee)\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\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\toutAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutFee := int64(outgoing.fee(outAmt))\n\tinboundBase := int64(incoming.inboundBase)\n\tinboundRate := int64(incoming.inboundRate)\n\n\tinboundFee := inboundBase +\n\t\t(int64(outAmt)+outFee)*inboundRate/1_000_000\n\n\ttotal := outFee + inboundFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype attemptMeta struct {\n\tdeliver lnwire.MilliSatoshi\n\tedgeKeys []edgeKey\n\tedgeAmounts []lnwire.MilliSatoshi\n\tobservedLoad []lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\troute *route.Route\n\tmeta *attemptMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[edgeKey]liquidityBelief\n\tpenalty map[edgeKey]float64\n\tbadEdges map[edgeKey]bool\n\n\tliveReserved map[edgeKey]lnwire.MilliSatoshi\n\tpending map[*route.Route]*attemptMeta\n\tplan []*plannedRoute\n\n\tcommittedFee lnwire.MilliSatoshi\n\toutstandingFee 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\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\tpenalty: make(map[edgeKey]float64),\n\t\tbadEdges: make(map[edgeKey]bool),\n\t\tliveReserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpending: make(map[*route.Route]*attemptMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tcapacity := lnwire.NewMSatFromSatoshis(ch.Capacity)\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tscope: source,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tcapacity: capacity,\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tbelief, ok := sharedBeliefs.values[key]\n\t\t\t\tsharedBeliefs.Unlock()\n\t\t\t\tif ok {\n\t\t\t\t\tr.beliefs[key] = belief\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 r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.012:\n\t\treturn 0.012\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.018)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.82)*18))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif load <= 0 || load > edge.capacity {\n\t\treturn 0.005\n\t}\n\tif edge.from == r.source {\n\t\treturn 0.995\n\t}\n\n\tprior := bimodalPrior(load, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && load >= belief.upperFail {\n\t\treturn 0.012\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK {\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(load-belief.lowerOK) / width\n\t\tevidence := 0.995*(1-position) + 0.012*position\n\t\tconfidence := math.Min(\n\t\t\t0.9,\n\t\t\t0.35+0.1*float64(belief.successes+belief.failures),\n\t\t)\n\t\treturn clampProbability(\n\t\t\tconfidence*evidence + (1-confidence)*prior,\n\t\t)\n\t}\n\n\tif belief.upperFail > 0 {\n\t\tratio := float64(load) / float64(belief.upperFail)\n\t\tif ratio > 0.2 {\n\t\t\tprior *= math.Exp(-2.0 * (ratio - 0.2))\n\t\t}\n\t\treturn clampProbability(prior)\n\t}\n\n\tif belief.lowerOK > 0 && load > belief.lowerOK {\n\t\tremaining := edge.capacity - belief.lowerOK\n\t\tif remaining <= 0 {\n\t\t\treturn 0.012\n\t\t}\n\t\tposition := float64(load-belief.lowerOK) / float64(remaining)\n\t\tevidence := 0.92 / (1 + math.Exp((position-0.72)*14))\n\t\treturn clampProbability(0.7*evidence + 0.3*prior)\n\t}\n\n\treturn prior\n}\n\nfunc applySuccess(b liquidityBelief,\n\tamt lnwire.MilliSatoshi) liquidityBelief {\n\n\tif amt > b.lowerOK {\n\t\tb.lowerOK = amt\n\t}\n\tif b.upperFail > 0 && amt >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\tb.successes++\n\treturn b\n}\n\nfunc applyFailure(b liquidityBelief,\n\tamt lnwire.MilliSatoshi) liquidityBelief {\n\n\tif b.upperFail == 0 || amt < b.upperFail {\n\t\tb.upperFail = amt\n\t}\n\tif b.lowerOK >= amt {\n\t\tb.lowerOK = amt * 3 / 4\n\t}\n\tb.failures++\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tr.beliefs[key] = applySuccess(r.beliefs[key], amt)\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = applySuccess(\n\t\tsharedBeliefs.values[key], amt,\n\t)\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tr.beliefs[key] = applyFailure(r.beliefs[key], amt)\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = applyFailure(\n\t\tsharedBeliefs.values[key], amt,\n\t)\n\tsharedBeliefs.Unlock()\n}\n\ntype searchLabel struct {\n\tedge *candidateEdge\n\tnext *searchLabel\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tindex int\n}\n\ntype searchQueue []*searchLabel\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *searchQueue) Push(value any) {\n\titem := value.(*searchLabel)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\told[last] = nil\n\t*q = old[:last]\n\treturn item\n}\n\nfunc pathContains(label *searchLabel, vertex route.Vertex) bool {\n\tfor current := label; current != nil; current = current.next {\n\t\tif current.edge.from == vertex || current.edge.to == vertex {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc copyReservations(src map[edgeKey]lnwire.MilliSatoshi) map[\n\tedgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) riskValue(maxFee lnwire.MilliSatoshi,\n\tlimited bool) float64 {\n\n\tif !limited {\n\t\treturn 420_000\n\t}\n\n\tvalue := float64(maxFee) / 2\n\tif value < 25_000 {\n\t\tvalue = 25_000\n\t}\n\tif value > 420_000 {\n\t\tvalue = 420_000\n\t}\n\treturn value\n}\n\nfunc (r *candidateRouter) edgeCost(edge *candidateEdge,\n\tload lnwire.MilliSatoshi, used map[edgeKey]int,\n\triskValue float64) float64 {\n\n\tprobability := r.edgeProbability(edge, load)\n\tcost := -math.Log(probability)*riskValue + r.penalty[edge.key]\n\n\tif count := used[edge.key]; count > 0 {\n\t\tcost += 360_000 * float64(count)\n\t}\n\n\tif edge.capacity > 0 {\n\t\tutilization := float64(load) / float64(edge.capacity)\n\t\tcost += utilization * utilization * 90_000\n\t}\n\n\treturn cost\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\tused map[edgeKey]int, maxFee lnwire.MilliSatoshi,\n\tlimited bool) (*plannedRoute, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid shard amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\trt := &route.Route{\n\t\t\tTotalTimeLock: finalCltvDelta,\n\t\t\tTotalAmount: deliver,\n\t\t\tSourcePubKey: r.source,\n\t\t}\n\t\treturn &plannedRoute{\n\t\t\troute: rt,\n\t\t\tmeta: &attemptMeta{\n\t\t\t\tdeliver: deliver,\n\t\t\t},\n\t\t}, nil\n\t}\n\n\triskValue := r.riskValue(maxFee, limited)\n\tqueue := &searchQueue{}\n\tbest := make(map[edgeKey]float64)\n\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tif r.badEdges[edge.key] || !edge.usable(deliver) {\n\t\t\tcontinue\n\t\t}\n\n\t\tload := deliver + reserved[edge.key]\n\t\tif load > edge.capacity {\n\t\t\tcontinue\n\t\t}\n\t\tif edge.from == r.source &&\n\t\t\tr.localBalances[edge.chanID] < load {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := r.edgeCost(edge, load, used, riskValue)\n\t\tlabel := &searchLabel{\n\t\t\tedge: edge,\n\t\t\tamount: deliver,\n\t\t\tscore: score,\n\t\t\thops: 1,\n\t\t}\n\t\tbest[edge.key] = score\n\t\theap.Push(queue, label)\n\t}\n\n\tvar result *searchLabel\n\tfor queue.Len() != 0 {\n\t\tcurrent := heap.Pop(queue).(*searchLabel)\n\t\tknown, ok := best[current.edge.key]\n\t\tif ok && current.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif current.edge.from == r.source {\n\t\t\tfee := current.amount - deliver\n\t\t\tif !limited || fee <= maxFee {\n\t\t\t\tresult = current\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif current.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tnode := current.edge.from\n\t\tfor _, incoming := range r.incomingEdges[node] {\n\t\t\tif r.badEdges[incoming.key] ||\n\t\t\t\tpathContains(current, incoming.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnodeFee := forwardingFee(\n\t\t\t\tincoming, current.edge, current.amount,\n\t\t\t)\n\t\t\tamount := current.amount + nodeFee\n\t\t\tif !incoming.usable(amount) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfeeSoFar := amount - deliver\n\t\t\tif limited && feeSoFar > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := amount + reserved[incoming.key]\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif incoming.from == r.source &&\n\t\t\t\tr.localBalances[incoming.chanID] < load {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := current.score + float64(nodeFee) +\n\t\t\t\tr.edgeCost(incoming, load, used, riskValue)\n\n\t\t\told, exists := best[incoming.key]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[incoming.key] = score\n\t\t\theap.Push(queue, &searchLabel{\n\t\t\t\tedge: incoming,\n\t\t\t\tnext: current,\n\t\t\t\tamount: amount,\n\t\t\t\tscore: score,\n\t\t\t\thops: current.hops + 1,\n\t\t\t})\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(deliver, result, reserved)\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tlabel *searchLabel,\n\treserved map[edgeKey]lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tvar (\n\t\tpath []*candidateEdge\n\t\tamounts []lnwire.MilliSatoshi\n\t)\n\tfor current := label; current != nil; current = current.next {\n\t\tpath = append(path, current.edge)\n\t\tamounts = append(amounts, current.amount)\n\t}\n\n\tif len(path) == 0 || path[len(path)-1].to != r.spec.Target {\n\t\treturn nil, errors.New(\"broken route\")\n\t}\n\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = finalCltvDelta\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tmeta := &attemptMeta{\n\t\tdeliver: deliver,\n\t\tedgeKeys: make([]edgeKey, len(path)),\n\t\tedgeAmounts: make([]lnwire.MilliSatoshi, len(path)),\n\t\tobservedLoad: make([]lnwire.MilliSatoshi, len(path)),\n\t\tfee: amounts[0] - deliver,\n\t}\n\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t\tmeta.edgeKeys[i] = edge.key\n\t\tmeta.edgeAmounts[i] = amounts[i]\n\t\tmeta.observedLoad[i] = amounts[i] + reserved[edge.key]\n\t}\n\n\trt := &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}\n\n\treturn &plannedRoute{\n\t\troute: rt,\n\t\tmeta: meta,\n\t\tscore: label.score,\n\t}, nil\n}\n\nfunc splitAmounts(total lnwire.MilliSatoshi, parts, mode int) (\n\t[]lnwire.MilliSatoshi, bool) {\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil, false\n\t}\n\n\tweights := make([]int64, parts)\n\tvar totalWeight int64\n\tfor i := 0; i < parts; i++ {\n\t\tswitch mode {\n\t\tcase 1:\n\t\t\tweights[i] = int64(parts - i)\n\t\tcase 2:\n\t\t\tvalue := int64(parts - i)\n\t\t\tweights[i] = value * value\n\t\tdefault:\n\t\t\tweights[i] = 1\n\t\t}\n\t\ttotalWeight += weights[i]\n\t}\n\n\ttotalValue := int64(total)\n\tbase := totalValue / totalWeight\n\tremainder := totalValue % totalWeight\n\tamounts := make([]lnwire.MilliSatoshi, parts)\n\n\tvar assigned int64\n\tfor i, weight := range weights {\n\t\tvalue := base*weight + remainder*weight/totalWeight\n\t\tif value <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\t\tamounts[i] = lnwire.MilliSatoshi(value)\n\t\tassigned += value\n\t}\n\tamounts[0] += lnwire.MilliSatoshi(totalValue - assigned)\n\n\treturn amounts, true\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn 0, false\n\t}\n\n\tspent := r.committedFee + r.outstandingFee\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0, true\n\t}\n\n\treturn r.spec.FeeLimitMsat - spent, true\n}\n\nfunc (r *candidateRouter) planVariant(\n\tamounts []lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi, limited bool) (\n\t[]*plannedRoute, float64, bool) {\n\n\treserved := copyReservations(r.liveReserved)\n\tused := make(map[edgeKey]int)\n\tfor key, amount := range reserved {\n\t\tif amount > 0 {\n\t\t\tused[key] = 1\n\t\t}\n\t}\n\n\tremainingFee := maxFee\n\tplan := make([]*plannedRoute, 0, len(amounts))\n\tvar totalScore float64\n\n\tfor _, amount := range amounts {\n\t\tcandidate, err := r.findRoute(\n\t\t\tamount, reserved, used, remainingFee, limited,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif limited {\n\t\t\tif candidate.meta.fee > remainingFee {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingFee -= candidate.meta.fee\n\t\t}\n\n\t\tplan = append(plan, candidate)\n\t\ttotalScore += candidate.score + 18_000\n\n\t\tfor i, key := range candidate.meta.edgeKeys {\n\t\t\treserved[key] += candidate.meta.edgeAmounts[i]\n\t\t\tused[key]++\n\t\t}\n\t}\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) createPlan(total lnwire.MilliSatoshi,\n\tpartsLeft uint32) error {\n\n\tif partsLeft == 0 {\n\t\treturn errors.New(\"maximum parts already in flight\")\n\t}\n\n\tmaxParts := int(partsLeft)\n\tif maxParts > 32 {\n\t\tmaxParts = 32\n\t}\n\n\tfeeBudget, limited := r.remainingFeeBudget()\n\tvar (\n\t\tbestPlan []*plannedRoute\n\t\tbestScore = math.Inf(1)\n\t)\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tmodes := 1\n\t\tif parts > 1 {\n\t\t\tmodes = 3\n\t\t}\n\n\t\tfor mode := 0; mode < modes; mode++ {\n\t\t\tamounts, ok := splitAmounts(total, parts, mode)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tplan, score, ok := r.planVariant(\n\t\t\t\tamounts, feeBudget, limited,\n\t\t\t)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif score < bestScore {\n\t\t\t\tbestScore = score\n\t\t\t\tbestPlan = plan\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(bestPlan) == 0 {\n\t\treturn errors.New(\"no feasible route set\")\n\t}\n\n\tr.plan = bestPlan\n\treturn 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 already satisfied\")\n\t}\n\tif r.attempts >= maxRouterTries {\n\t\treturn nil, errors.New(\"routing attempt limit reached\")\n\t}\n\n\tif len(r.plan) > 0 {\n\t\tnext := r.plan[0]\n\t\tremainingFee, limited := r.remainingFeeBudget()\n\t\tif next.meta.deliver > amt ||\n\t\t\t(limited && next.meta.fee > remainingFee) {\n\n\t\t\tr.plan = nil\n\t\t}\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t\t}\n\n\t\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\t\tif err := r.createPlan(amt, partsLeft); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\n\tfor i, key := range next.meta.edgeKeys {\n\t\tr.liveReserved[key] += next.meta.edgeAmounts[i]\n\t}\n\tr.pending[next.route] = next.meta\n\tr.outstandingFee += next.meta.fee\n\tr.attempts++\n\n\treturn next.route, nil\n}\n\nfunc (r *candidateRouter) release(meta *attemptMeta) {\n\tfor i, key := range meta.edgeKeys {\n\t\tamount := meta.edgeAmounts[i]\n\t\tif r.liveReserved[key] <= amount {\n\t\t\tdelete(r.liveReserved, key)\n\t\t} else {\n\t\t\tr.liveReserved[key] -= amount\n\t\t}\n\t}\n}\n\nfunc failureIndex(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tmeta := r.pending[rt]\n\tdelete(r.pending, rt)\n\n\tif meta != nil {\n\t\tif r.outstandingFee <= meta.fee {\n\t\t\tr.outstandingFee = 0\n\t\t} else {\n\t\t\tr.outstandingFee -= meta.fee\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tif meta == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tr.committedFee += meta.fee\n\t\tfor i, key := range meta.edgeKeys {\n\t\t\tr.learnSuccess(key, meta.observedLoad[i])\n\t\t\tr.penalty[key] *= 0.35\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tif meta != nil {\n\t\tr.release(meta)\n\t}\n\n\tfailIdx := failureIndex(rt, result.FailureSource)\n\tcode := result.Failure.Code()\n\n\tif meta == nil {\n\t\treturn nil\n\t}\n\n\tif failIdx > len(meta.edgeKeys) {\n\t\tfailIdx = -1\n\t}\n\n\tif failIdx >= 0 {\n\t\tfor i := 0; i < failIdx && i < len(meta.edgeKeys); i++ {\n\t\t\tr.learnSuccess(\n\t\t\t\tmeta.edgeKeys[i], meta.observedLoad[i],\n\t\t\t)\n\t\t}\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif failIdx >= 0 && failIdx < len(meta.edgeKeys) {\n\t\t\tkey := meta.edgeKeys[failIdx]\n\t\t\tr.learnFailure(key, meta.observedLoad[failIdx])\n\t\t\tr.penalty[key] += 140_000\n\t\t} else {\n\t\t\tfor _, key := range meta.edgeKeys {\n\t\t\t\tr.penalty[key] += 30_000\n\t\t\t}\n\t\t}\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tif failIdx >= 0 && failIdx < len(meta.edgeKeys) {\n\t\t\tkey := meta.edgeKeys[failIdx]\n\t\t\tr.badEdges[key] = true\n\t\t\tr.penalty[key] += 1_000_000\n\t\t}\n\n\tdefault:\n\t\tif failIdx >= 0 && failIdx < len(meta.edgeKeys) {\n\t\t\tr.penalty[meta.edgeKeys[failIdx]] += 100_000\n\t\t} else {\n\t\t\tfor _, key := range meta.edgeKeys {\n\t\t\t\tr.penalty[key] += 20_000\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nvar _ routing.SimRouter = (*candidateRouter)(nil)\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\n\t\t\"candidate-router(source=%v,target=%v,attempts=%d)\",\n\t\tr.source, r.spec.Target, r.attempts,\n\t)\n}"
}
},
{
"id": 27,
"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\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(20_000)\n\thopCostMsat = float64(6_000)\n\treusedEdgeCostMsat = float64(1_800_000)\n\tfailedLoadCostMsat = float64(6_000_000)\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 10\n\treservationLifetime = 45 * time.Second\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype transientReservation struct {\n\tamount lnwire.MilliSatoshi\n\tupdated time.Time\n}\n\nvar sharedReservations = struct {\n\tsync.Mutex\n\tnextOwner uint64\n\tbyEdge map[edgeKey]map[uint64]transientReservation\n}{\n\tbyEdge: make(map[edgeKey]map[uint64]transientReservation),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\n\t// The inbound fee is charged on the amount forwarded plus the\n\t// forwarding node's outbound fee.\n\tbasis := int64(amt) + outbound\n\tinbound := int64(in.inboundBase) +\n\t\tbasis*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tsignature uint64\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\towner uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[edgeKey]float64\n\tfailedAt map[edgeKey]lnwire.MilliSatoshi\n\tfailureHits map[edgeKey]int\n\tpathPenalty map[uint64]float64\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\tcommittedFees lnwire.MilliSatoshi\n\tsuccessfulParts uint32\n\tnumFailures int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tsharedReservations.Lock()\n\tsharedReservations.nextOwner++\n\towner := sharedReservations.nextOwner\n\tsharedReservations.Unlock()\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\towner: owner,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tfailedAt: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tfailureHits: make(map[edgeKey]int),\n\t\tpathPenalty: make(map[uint64]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\nfunc addAmounts(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\told := dst[edge.key]\n\t\tif meta.amounts[i] > lnwire.MaxMilliSatoshi-old {\n\t\t\tdst[edge.key] = lnwire.MaxMilliSatoshi\n\t\t} else {\n\t\t\tdst[edge.key] = old + meta.amounts[i]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ownReservations() map[edgeKey]lnwire.MilliSatoshi {\n\tresult := make(map[edgeKey]lnwire.MilliSatoshi, len(r.reserved))\n\tfor key, amount := range r.reserved {\n\t\tresult[key] = amount\n\t}\n\tfor _, meta := range r.issued {\n\t\taddAmounts(result, meta)\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) syncSharedReservations(now time.Time) {\n\town := r.ownReservations()\n\n\tsharedReservations.Lock()\n\tdefer sharedReservations.Unlock()\n\n\tfor key, owners := range sharedReservations.byEdge {\n\t\tentry, exists := owners[r.owner]\n\t\tamount, wanted := own[key]\n\n\t\tif wanted && amount > 0 {\n\t\t\towners[r.owner] = transientReservation{\n\t\t\t\tamount: amount,\n\t\t\t\tupdated: now,\n\t\t\t}\n\t\t} else if exists {\n\t\t\tdelete(owners, r.owner)\n\t\t}\n\n\t\tfor owner, reservation := range owners {\n\t\t\tif now.Before(reservation.updated) ||\n\t\t\t\tnow.Sub(reservation.updated) > reservationLifetime {\n\n\t\t\t\tdelete(owners, owner)\n\t\t\t}\n\t\t}\n\t\tif len(owners) == 0 {\n\t\t\tdelete(sharedReservations.byEdge, key)\n\t\t}\n\t}\n\n\tfor key, amount := range own {\n\t\tif amount <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\towners := sharedReservations.byEdge[key]\n\t\tif owners == nil {\n\t\t\towners = make(map[uint64]transientReservation)\n\t\t\tsharedReservations.byEdge[key] = owners\n\t\t}\n\t\towners[r.owner] = transientReservation{\n\t\t\tamount: amount,\n\t\t\tupdated: now,\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) externalReservations(\n\tnow time.Time) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[edgeKey]lnwire.MilliSatoshi)\n\n\tsharedReservations.Lock()\n\tdefer sharedReservations.Unlock()\n\n\tfor key, owners := range sharedReservations.byEdge {\n\t\tfor owner, reservation := range owners {\n\t\t\tif now.Before(reservation.updated) ||\n\t\t\t\tnow.Sub(reservation.updated) > reservationLifetime {\n\n\t\t\t\tdelete(owners, owner)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif owner == r.owner {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\told := result[key]\n\t\t\tif reservation.amount > lnwire.MaxMilliSatoshi-old {\n\t\t\t\tresult[key] = lnwire.MaxMilliSatoshi\n\t\t\t} else {\n\t\t\t\tresult[key] = old + reservation.amount\n\t\t\t}\n\t\t}\n\t\tif len(owners) == 0 {\n\t\t\tdelete(sharedReservations.byEdge, key)\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amount := range src {\n\t\tdst[key] = amount\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) baseReservations(\n\tnow time.Time) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := r.ownReservations()\n\tfor key, amount := range r.externalReservations(now) {\n\t\told := result[key]\n\t\tif amount > lnwire.MaxMilliSatoshi-old {\n\t\t\tresult[key] = lnwire.MaxMilliSatoshi\n\t\t} else {\n\t\t\tresult[key] = old + amount\n\t\t}\n\t}\n\treturn result\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc mixHash(hash, value uint64) uint64 {\n\thash ^= value\n\thash *= 1099511628211\n\treturn hash\n}\n\nfunc statePathHash(first *candidateEdge, remainder *searchState) uint64 {\n\thash := uint64(1469598103934665603)\n\thash = mixHash(hash, first.chanID)\n\n\tfor cur := remainder; cur != nil && cur.nextEdge != nil;\n\t\tcur = cur.child {\n\n\t\thash = mixHash(hash, cur.nextEdge.chanID)\n\t}\n\treturn hash\n}\n\nfunc pathHash(path []*candidateEdge) uint64 {\n\thash := uint64(1469598103934665603)\n\tfor _, edge := range path {\n\t\thash = mixHash(hash, edge.chanID)\n\t}\n\treturn hash\n}\n\nfunc riskPrice(feeAllowance lnwire.MilliSatoshi) float64 {\n\tif feeAllowance == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeAllowance) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap, riskAllowance lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(riskAllowance)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\tif reserved > lnwire.MaxMilliSatoshi-over {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.985\n\t\t\t}\n\n\t\t\tscore := cur.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reserved > 0 {\n\t\t\t\tscore += reusedEdgeCostMsat\n\t\t\t}\n\n\t\t\tif ceiling := r.failedAt[edge.key]; ceiling > 0 &&\n\t\t\t\ttotalLoad >= ceiling {\n\n\t\t\t\tscore += failedLoadCostMsat +\n\t\t\t\t\tfloat64(r.failureHits[edge.key])*400_000\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tsignature := statePathHash(edge, cur)\n\t\t\t\tscore += r.pathPenalty[signature]\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\treserved := reservations[edge.key]\n\t\tif reserved > lnwire.MaxMilliSatoshi-amounts[i] {\n\t\t\treturn nil, errors.New(\"route load overflow\")\n\t\t}\n\t\tloads[i] = amounts[i] + reserved\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t\tsignature: pathHash(path),\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tfloat64(total) * float64(weight) / float64(sum),\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - lnwire.MilliSatoshi(len(weights)-i-1)\n\t\t}\n\t\tif part <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 10\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tif parts > 1 {\n\t\tascending := make([]int64, parts)\n\t\tdescending := make([]int64, parts)\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tascending[i] = int64(i + 1)\n\t\t\tdescending[i] = int64(parts - i)\n\t\t}\n\t\tpatterns = append(\n\t\t\tpatterns,\n\t\t\tweightedAllocation(total, ascending),\n\t\t\tweightedAllocation(total, descending),\n\t\t)\n\t}\n\n\thotCount := parts\n\tif hotCount > 6 {\n\t\thotCount = 3\n\t}\n\n\tfor ratio := int64(2); ratio <= 3; ratio++ {\n\t\tfor j := 0; j < hotCount; j++ {\n\t\t\thot := j\n\t\t\tif parts > 6 {\n\t\t\t\tswitch j {\n\t\t\t\tcase 0:\n\t\t\t\t\thot = 0\n\t\t\t\tcase 1:\n\t\t\t\t\thot = parts / 2\n\t\t\t\tdefault:\n\t\t\t\t\thot = parts - 1\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tweights := make([]int64, parts)\n\t\t\tfor i := range weights {\n\t\t\t\tweights[i] = 10\n\t\t\t}\n\t\t\tweights[hot] = 10 * ratio\n\t\t\tpatterns = append(\n\t\t\t\tpatterns,\n\t\t\t\tweightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc ratioAmount(value, part,\n\ttotal lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif total <= 0 || value <= 0 || part <= 0 {\n\t\treturn 0\n\t}\n\tif part >= total {\n\t\treturn value\n\t}\n\n\tresult := lnwire.MilliSatoshi(\n\t\tfloat64(value) * float64(part) / float64(total),\n\t)\n\tif result < 0 {\n\t\treturn 0\n\t}\n\tif result > value {\n\t\treturn value\n\t}\n\treturn result\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\told := dst[edge.key]\n\t\tif meta.amounts[i] > lnwire.MaxMilliSatoshi-old {\n\t\t\tdst[edge.key] = lnwire.MaxMilliSatoshi\n\t\t} else {\n\t\t\tdst[edge.key] = old + meta.amounts[i]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tbase map[edgeKey]lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(base)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\triskAllowance := lnwire.MaxMilliSatoshi\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\triskAllowance = ratioAmount(\n\t\t\t\tremainingBudget, shard, remainingAmount,\n\t\t\t)\n\t\t}\n\n\t\tpr, err := r.findRoute(\n\t\t\tshard, reservations, remainingBudget, riskAllowance,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * 30_000\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tbase := r.baseReservations(now)\n\tfeeBudget := r.remainingFeeBudget()\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tif len(allocation) != parts {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, base, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\t// If a complete static plan cannot be formed, send a shard that is\n\t// still large enough for the remaining part count to finish. Starting\n\t// at the minimum viable size also gives failed corridors a genuine\n\t// lower-amount retry instead of repeating the failed load.\n\tif slots > 1 {\n\t\tfor k := uint32(1); k < slots; k++ {\n\t\t\tshard := lnwire.MilliSatoshi(\n\t\t\t\tmath.Ceil(\n\t\t\t\t\tfloat64(total) * float64(k) /\n\t\t\t\t\t\tfloat64(slots),\n\t\t\t\t),\n\t\t\t)\n\t\t\tif shard <= 0 || shard >= total {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\triskAllowance := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\triskAllowance = ratioAmount(\n\t\t\t\t\tfeeBudget, shard, total,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(\n\t\t\t\tshard, base, feeBudget, riskAllowance,\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tnow := r.view.Now()\n\tr.syncSharedReservations(now)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tused := r.successfulParts\n\tif inFlightHtlcs > used {\n\t\tused = inFlightHtlcs\n\t}\n\tif used >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - used\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.syncSharedReservations(now)\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\tbase := r.baseReservations(r.view.Now())\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tload := amount\n\t\tif base[key] <= lnwire.MaxMilliSatoshi-amount {\n\t\t\tload += base[key]\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, load)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t\tsignature: pathHash(path),\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\tr.syncSharedReservations(r.view.Now())\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\n\t\t\told := r.reserved[edge.key]\n\t\t\tif meta.amounts[i] > lnwire.MaxMilliSatoshi-old {\n\t\t\t\tr.reserved[edge.key] = lnwire.MaxMilliSatoshi\n\t\t\t} else {\n\t\t\t\tr.reserved[edge.key] = old + meta.amounts[i]\n\t\t\t}\n\n\t\t\tr.edgePenalty[edge.key] *= 0.25\n\t\t\tif ceiling := r.failedAt[edge.key]; ceiling > 0 &&\n\t\t\t\tmeta.loads[i] >= ceiling {\n\n\t\t\t\tdelete(r.failedAt, edge.key)\n\t\t\t\tdelete(r.failureHits, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.pathPenalty[meta.signature] *= 0.2\n\t\tr.committedFees += meta.fee\n\t\tr.successfulParts++\n\t\tr.syncSharedReservations(now)\n\t\treturn nil\n\t}\n\n\tr.numFailures++\n\tr.plan = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tcode := result.Failure.Code()\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tload := meta.loads[failIdx]\n\t\t\tupdateBelief(edge.key, load, false, now)\n\n\t\t\told := r.failedAt[edge.key]\n\t\t\tif old == 0 || load < old {\n\t\t\t\tr.failedAt[edge.key] = load\n\t\t\t}\n\t\t\tr.failureHits[edge.key]++\n\t\t\tr.edgePenalty[edge.key] += 50_000\n\t\t\tr.pathPenalty[meta.signature] += 250_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\t\t\tr.pathPenalty[meta.signature] += 4_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 1_500_000\n\t\t\tr.pathPenalty[meta.signature] += 1_500_000\n\t\t}\n\t} else {\n\t\t// Unknown-source failures must diversify the whole route. They do\n\t\t// not establish a liquidity bound on any particular channel.\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tr.pathPenalty[meta.signature] += 3_000_000\n\t\t\tfor _, edge := range meta.path {\n\t\t\t\tr.edgePenalty[edge.key] += 300_000\n\t\t\t}\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.pathPenalty[meta.signature] += 6_000_000\n\t\t\tfor _, edge := range meta.path {\n\t\t\t\tr.edgePenalty[edge.key] += 750_000\n\t\t\t}\n\n\t\tdefault:\n\t\t\tr.pathPenalty[meta.signature] += 2_000_000\n\t\t\tfor _, edge := range meta.path {\n\t\t\t\tr.edgePenalty[edge.key] += 200_000\n\t\t\t}\n\t\t}\n\t}\n\n\tr.syncSharedReservations(now)\n\treturn nil\n}"
}
},
{
"id": 28,
"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\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\thopCostMsat = float64(8_000)\n\treusedEdgeCostMsat = float64(700_000)\n\tpartCostMsat = float64(35_000)\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 8\n\tmaxFailedAttempts = uint32(24)\n\tmaxIssuedAttempts = uint32(32)\n\tmaxDuplicateReplans = 5\n\tminAdaptiveShardMsat = lnwire.MilliSatoshi(1_000)\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := out.fee(amt)\n\tif outbound > lnwire.MaxMilliSatoshi-amt {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\t// The inbound rate applies to the amount arriving at the forwarding\n\t// node: its outgoing amount plus its outbound forwarding fee.\n\tinboundBasis := amt + outbound\n\tinbound := int64(in.inboundBase) +\n\t\tint64(inboundBasis)*int64(in.inboundRate)/1_000_000\n\n\ttotal := int64(outbound) + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\tif total > int64(lnwire.MaxMilliSatoshi) {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[edgeKey]float64\n\tissued map[*route.Route]*routeMeta\n\tattempted map[uint64]uint32\n\tplan []*plannedRoute\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\tretryCeiling lnwire.MilliSatoshi\n\tsuccessfulParts uint32\n\tfailedAttempts uint32\n\tissuedAttempts 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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\n\t\tattempted: make(map[uint64]uint32),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t\tif b.conf < 0.05 {\n\t\t\tb = liquidityBelief{}\n\t\t}\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int { return len(q) }\nfunc (q searchQueue) Less(i, j int) bool { return q[i].score < q[j].score }\nfunc (q searchQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc cloneReservations(src map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 && r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLoad := over + reservations[edge.key]\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.985\n\t\t\t}\n\n\t\t\tscore := cur.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.45\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\twhole := total / lnwire.MilliSatoshi(sum)\n\tremainder := total % lnwire.MilliSatoshi(sum)\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := whole*lnwire.MilliSatoshi(weight) +\n\t\t\tremainder*lnwire.MilliSatoshi(weight)/\n\t\t\t\tlnwire.MilliSatoshi(sum)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int, wide bool) [][]lnwire.MilliSatoshi {\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\tif !wide {\n\t\treturn patterns\n\t}\n\n\tvar variants [][]int64\n\tswitch parts {\n\tcase 2:\n\t\tvariants = [][]int64{{35, 65}, {65, 35}}\n\tcase 3:\n\t\tvariants = [][]int64{\n\t\t\t{20, 30, 50}, {50, 30, 20},\n\t\t}\n\tcase 4:\n\t\tvariants = [][]int64{\n\t\t\t{15, 20, 25, 40}, {40, 25, 20, 15},\n\t\t}\n\tdefault:\n\t\tif parts > 4 && parts <= 8 {\n\t\t\tup := make([]int64, parts)\n\t\t\tdown := make([]int64, parts)\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tup[i] = int64(i + 1)\n\t\t\t\tdown[i] = int64(parts - i)\n\t\t\t}\n\t\t\tvariants = [][]int64{up, down}\n\t\t}\n\t}\n\n\tfor _, weights := range variants {\n\t\tpatterns = append(\n\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t)\n\t}\n\treturn patterns\n}\n\nfunc ceilingAllocation(total, ceiling lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif ceiling <= 0 || ceiling >= total || slots < 2 {\n\t\treturn nil\n\t}\n\n\tparts := (total + ceiling - 1) / ceiling\n\tif parts < 2 || parts > lnwire.MilliSatoshi(slots) {\n\t\treturn nil\n\t}\n\n\tweights := make([]int64, int(parts))\n\tfor i := range weights {\n\t\tweights[i] = 1\n\t}\n\treturn weightedAllocation(total, weights)\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, remainingBudget)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t}\n\n\ttotalScore += float64(len(plan)-1) * partCostMsat\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) largestRoutable(\n\tlimit lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif limit < minAdaptiveShardMsat {\n\t\treturn nil, errors.New(\"amount below adaptive minimum\")\n\t}\n\n\tfirst, err := r.findRoute(limit, reservations, feeBudget)\n\tif err == nil {\n\t\treturn first, nil\n\t}\n\n\tbad := limit\n\tprobe := limit\n\tvar best *plannedRoute\n\tvar good lnwire.MilliSatoshi\n\n\tfor i := 0; i < 18; i++ {\n\t\tnext := probe * 3 / 4\n\t\tif next >= probe {\n\t\t\tnext = probe - 1\n\t\t}\n\t\tif next < minAdaptiveShardMsat {\n\t\t\tnext = minAdaptiveShardMsat\n\t\t}\n\n\t\tpr, findErr := r.findRoute(next, reservations, feeBudget)\n\t\tif findErr == nil {\n\t\t\tbest = pr\n\t\t\tgood = next\n\t\t\tbreak\n\t\t}\n\n\t\tbad = next\n\t\tprobe = next\n\t\tif next == minAdaptiveShardMsat {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no adaptive shard found\")\n\t}\n\n\t// Route feasibility is nearly monotone above the public HTLC floors.\n\t// Refine the largest feasible shard within the successful bracket.\n\tfor i := 0; i < 8 && bad-good > 1; i++ {\n\t\tmid := good + (bad-good)/2\n\t\tpr, findErr := r.findRoute(mid, reservations, feeBudget)\n\t\tif findErr == nil {\n\t\t\tbest = pr\n\t\t\tgood = mid\n\t\t} else {\n\t\t\tbad = mid\n\t\t}\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) greedyPlan(total lnwire.MilliSatoshi,\n\tslots uint32, feeBudget lnwire.MilliSatoshi) ([]*plannedRoute,\n\tfloat64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremaining := total\n\tremainingBudget := feeBudget\n\tplan := make([]*plannedRoute, 0, slots)\n\ttotalScore := float64(0)\n\n\tfor left := slots; left > 0 && remaining > 0; left-- {\n\t\tpr, err := r.largestRoutable(\n\t\t\tremaining, reservations, remainingBudget,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif pr.meta.delivered <= 0 ||\n\t\t\tpr.meta.delivered > remaining {\n\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremaining -= pr.meta.delivered\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, 0, false\n\t}\n\n\ttotalScore += float64(len(plan)-1) * partCostMsat\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\twide := r.failedAttempts < 3\n\n\tmaxEnumerated := int(slots)\n\tif maxEnumerated > 8 {\n\t\tmaxEnumerated = 8\n\t}\n\n\tfor parts := 1; parts <= maxEnumerated; parts++ {\n\t\tfor _, allocation := range allocationPatterns(\n\t\t\ttotal, parts, wide,\n\t\t) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif int(slots) > maxEnumerated {\n\t\tallocation := allocationPatterns(\n\t\t\ttotal, int(slots), false,\n\t\t)[0]\n\t\tplan, score, ok := r.tryAllocation(allocation, feeBudget)\n\t\tif ok && score < bestScore {\n\t\t\tbest = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif allocation := ceilingAllocation(\n\t\ttotal, r.retryCeiling, slots,\n\t); allocation != nil {\n\n\t\tplan, score, ok := r.tryAllocation(allocation, feeBudget)\n\t\tif ok && score < bestScore {\n\t\t\tbest = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif len(best) != 0 {\n\t\treturn best, nil\n\t}\n\n\t// This fallback derives unequal shards from the actual route ceilings\n\t// exposed by gossip instead of relying only on fixed ratios.\n\tif plan, _, ok := r.greedyPlan(total, slots, feeBudget); ok {\n\t\treturn plan, nil\n\t}\n\n\t// If a complete set cannot be committed, send one bounded shard so\n\t// feedback can still make progress without an unbounded halving loop.\n\tif slots > 1 {\n\t\tshard := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\t\tif r.retryCeiling > 0 && shard > r.retryCeiling {\n\t\t\tshard = r.retryCeiling\n\t\t}\n\n\t\tfor i := 0; i < 8 && shard >= minAdaptiveShardMsat; i++ {\n\t\t\tcap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tcap = feeBudget * shard / total\n\t\t\t\tif cap == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\t\t\tshard = shard * 2 / 3\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc routeFingerprint(meta *routeMeta) uint64 {\n\th := uint64(1469598103934665603)\n\th ^= uint64(meta.delivered)\n\th *= 1099511628211\n\tfor _, edge := range meta.path {\n\t\th ^= edge.chanID\n\t\th *= 1099511628211\n\t\th ^= uint64(edge.from[0])<<8 | uint64(edge.to[0])\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.failedAttempts >= maxFailedAttempts ||\n\t\tr.issuedAttempts >= maxIssuedAttempts {\n\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tused := r.successfulParts\n\tif inFlightHtlcs > used {\n\t\tused = inFlightHtlcs\n\t}\n\tif used >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - used\n\n\tfor replan := 0; replan < maxDuplicateReplans; replan++ {\n\t\tif len(r.plan) != 0 && queuedAmount(r.plan) != amt {\n\t\t\tr.plan = nil\n\t\t}\n\n\t\tif len(r.plan) == 0 {\n\t\t\tplan, err := r.makePlan(amt, slots)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr.plan = plan\n\t\t}\n\n\t\tnext := r.plan[0]\n\t\tfingerprint := routeFingerprint(next.meta)\n\t\tif r.attempted[fingerprint] >= 2 {\n\t\t\tfor _, edge := range next.meta.path {\n\t\t\t\tr.edgePenalty[edge.key] += 1_500_000\n\t\t\t}\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tr.plan = r.plan[1:]\n\t\tr.attempted[fingerprint]++\n\t\tr.issuedAttempts++\n\t\tr.issued[next.rt] = next.meta\n\t\treturn next.rt, nil\n\t}\n\n\treturn nil, errors.New(\"no untried route found\")\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.35\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.successfulParts++\n\t\treturn nil\n\t}\n\n\tr.failedAttempts++\n\tr.plan = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t\tr.edgePenalty[meta.path[i].key] *= 0.75\n\t}\n\n\tcode := result.Failure.Code()\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.edgePenalty[edge.key] += 900_000\n\n\t\t\tlower := meta.delivered * 2 / 3\n\t\t\tif lower < minAdaptiveShardMsat {\n\t\t\t\tlower = minAdaptiveShardMsat\n\t\t\t}\n\t\t\tif r.retryCeiling == 0 || lower < r.retryCeiling {\n\t\t\t\tr.retryCeiling = lower\n\t\t\t}\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_000_000\n\t\t}\n\t} else {\n\t\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\t\tguard := meta.fee / 20\n\t\t\tif guard < 1_000 {\n\t\t\t\tguard = 1_000\n\t\t\t}\n\t\t\tr.budgetGuard += guard\n\t\t}\n\t\tfor _, edge := range meta.path {\n\t\t\tr.edgePenalty[edge.key] += 150_000\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 29,
"parent": 1,
"score": 0.3872,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\thopCostMsat = float64(4_000)\n\treusedEdgeCostMsat = float64(800_000)\n\tadditionalPartCost = float64(90_000)\n\tmaxSearchHops = 64\n\tmaxLabelsPerNode = 14\n\tmaxLiquidityPenalty = float64(8_000_000)\n\tmaxPolicyPenalty = float64(20_000_000)\n\tbeliefHalfLifeSeconds = float64(1800)\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype edgeFailureCost struct {\n\tcost float64\n\tfailedAmt lnwire.MilliSatoshi\n\tupdated time.Time\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tliquidityPenalty map[edgeKey]edgeFailureCost\n\tpolicyPenalty map[edgeKey]float64\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\tsuccessfulParts uint32\n\tunknownFailures 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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tliquidityPenalty: make(map[edgeKey]edgeFailureCost),\n\t\tpolicyPenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated).Seconds()\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\treturn b.conf * math.Exp(-age/beliefHalfLifeSeconds)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int { return len(q) }\nfunc (q searchQueue) Less(i, j int) bool { return q[i].score < q[j].score }\nfunc (q searchQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tcheapest := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[cheapest].fee {\n\t\t\t\tcheapest = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == cheapest {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) edgeFailurePenalty(key edgeKey,\n\tload lnwire.MilliSatoshi, now time.Time) float64 {\n\n\tstate, ok := r.liquidityPenalty[key]\n\tif !ok || state.cost <= 0 {\n\t\treturn 0\n\t}\n\n\tcost := state.cost\n\tif !state.updated.IsZero() && !now.Before(state.updated) {\n\t\tcost *= math.Exp(-now.Sub(state.updated).Seconds() / 900)\n\t}\n\n\tif state.failedAmt > 0 && load < state.failedAmt {\n\t\tratio := float64(load) / float64(state.failedAmt)\n\t\tcost *= ratio * ratio * ratio\n\t}\n\n\treturn cost\n}\n\nfunc (r *candidateRouter) recordLiquidityFailure(key edgeKey,\n\tload lnwire.MilliSatoshi, now time.Time, strength float64) {\n\n\tstate := r.liquidityPenalty[key]\n\tif !state.updated.IsZero() && !now.Before(state.updated) {\n\t\tstate.cost *= math.Exp(-now.Sub(state.updated).Seconds() / 900)\n\t}\n\n\tstate.cost = state.cost*0.55 + strength\n\tif state.cost > maxLiquidityPenalty {\n\t\tstate.cost = maxLiquidityPenalty\n\t}\n\tif load > 0 {\n\t\tstate.failedAmt = load\n\t}\n\tstate.updated = now\n\tr.liquidityPenalty[key] = state\n}\n\nfunc (r *candidateRouter) recordEdgeSuccess(key edgeKey,\n\tload lnwire.MilliSatoshi, now time.Time) {\n\n\tstate, ok := r.liquidityPenalty[key]\n\tif !ok {\n\t\treturn\n\t}\n\n\tstate.cost *= 0.18\n\tif state.failedAmt != 0 && load >= state.failedAmt {\n\t\tstate.failedAmt = 0\n\t}\n\tstate.updated = now\n\n\tif state.cost < 1 {\n\t\tdelete(r.liquidityPenalty, key)\n\t\treturn\n\t}\n\tr.liquidityPenalty[key] = state\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.985\n\t\t\t}\n\n\t\t\tscore := cur.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.policyPenalty[edge.key] +\n\t\t\t\tr.edgeFailurePenalty(edge.key, totalLoad, now)\n\n\t\t\tif reserved > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.45\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tfloat64(total) * float64(weight) / float64(sum),\n\t\t)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\n\t\tminTail := lnwire.MilliSatoshi(len(weights) - i - 1)\n\t\tif part > remaining-minTail {\n\t\t\tpart = remaining - minTail\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t}\n\n\treturn result\n}\n\nfunc allocationFingerprint(parts []lnwire.MilliSatoshi) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, part := range parts {\n\t\tv := uint64(part)\n\t\tfor i := 0; i < 8; i++ {\n\t\t\th ^= v & 0xff\n\t\t\th *= 1099511628211\n\t\t\tv >>= 8\n\t\t}\n\t}\n\treturn h\n}\n\nfunc appendAllocation(patterns *[][]lnwire.MilliSatoshi,\n\tseen map[uint64]bool, total lnwire.MilliSatoshi, weights []int64) {\n\n\tallocation := weightedAllocation(total, weights)\n\tfor _, part := range allocation {\n\t\tif part <= 0 {\n\t\t\treturn\n\t\t}\n\t}\n\n\tkey := allocationFingerprint(allocation)\n\tif seen[key] {\n\t\treturn\n\t}\n\tseen[key] = true\n\t*patterns = append(*patterns, allocation)\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tpatterns := make([][]lnwire.MilliSatoshi, 0)\n\tseen := make(map[uint64]bool)\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\tappendAllocation(&patterns, seen, total, equal)\n\n\tif parts == 1 {\n\t\treturn patterns\n\t}\n\n\tif parts == 2 {\n\t\tfor pct := int64(10); pct <= 90; pct += 5 {\n\t\t\tappendAllocation(\n\t\t\t\t&patterns, seen, total,\n\t\t\t\t[]int64{pct, 100 - pct},\n\t\t\t)\n\t\t}\n\t\treturn patterns\n\t}\n\n\tascending := make([]int64, parts)\n\tdescending := make([]int64, parts)\n\tfor i := 0; i < parts; i++ {\n\t\tascending[i] = int64(i + 1)\n\t\tdescending[i] = int64(parts - i)\n\t}\n\tappendAllocation(&patterns, seen, total, ascending)\n\tappendAllocation(&patterns, seen, total, descending)\n\n\theavyValues := []int64{2, 3, 5}\n\tif parts > 4 {\n\t\theavyValues = []int64{3}\n\t}\n\n\tfor _, heavy := range heavyValues {\n\t\tindices := []int{0, parts - 1}\n\t\tif parts <= 4 {\n\t\t\tindices = indices[:0]\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tindices = append(indices, i)\n\t\t\t}\n\t\t}\n\n\t\tfor _, idx := range indices {\n\t\t\tweights := make([]int64, parts)\n\t\t\tfor i := range weights {\n\t\t\t\tweights[i] = 1\n\t\t\t}\n\t\t\tweights[idx] = heavy\n\t\t\tappendAllocation(&patterns, seen, total, weights)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\tfor _, shard := range allocation {\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor i, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsearchCap := remainingBudget\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\ti < len(allocation)-1 {\n\n\t\t\tproportional := lnwire.MilliSatoshi(\n\t\t\t\tfloat64(remainingBudget) *\n\t\t\t\t\tfloat64(shard) / float64(remainingAmount),\n\t\t\t)\n\t\t\tslack := remainingBudget / 12\n\t\t\tif proportional <= lnwire.MaxMilliSatoshi-slack {\n\t\t\t\tproportional += slack\n\t\t\t}\n\t\t\tif proportional < searchCap {\n\t\t\t\tsearchCap = proportional\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, searchCap)\n\t\tif err != nil && searchCap != remainingBudget {\n\t\t\tpr, err = r.findRoute(\n\t\t\t\tshard, reservations, remainingBudget,\n\t\t\t)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * additionalPartCost\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 && r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(allocation, feeBudget)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\tif slots > 1 {\n\t\tcandidates := make([]lnwire.MilliSatoshi, 0, 20)\n\t\tfor divisor := lnwire.MilliSatoshi(slots);\n\t\t\tdivisor <= lnwire.MilliSatoshi(slots)*8;\n\t\t\tdivisor++ {\n\n\t\t\tshard := (total + divisor - 1) / divisor\n\t\t\tif shard > 0 {\n\t\t\t\tcandidates = append(candidates, shard)\n\t\t\t}\n\t\t}\n\n\t\tshard := total / 2\n\t\tfor shard >= 1_000 {\n\t\t\tcandidates = append(candidates, shard)\n\t\t\tshard /= 2\n\t\t}\n\n\t\tfor _, candidate := range candidates {\n\t\t\tcap := feeBudget\n\t\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tcap = lnwire.MilliSatoshi(\n\t\t\t\t\tfloat64(feeBudget) *\n\t\t\t\t\t\tfloat64(candidate) / float64(total),\n\t\t\t\t)\n\t\t\t\tcap += feeBudget / 10\n\t\t\t\tif cap > feeBudget {\n\t\t\t\t\tcap = feeBudget\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpr, err := r.findRoute(candidate, r.reserved, cap)\n\t\t\tif err == nil {\n\t\t\t\treturn []*plannedRoute{pr}, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tused := r.successfulParts\n\tif inFlightHtlcs > used {\n\t\tused = inFlightHtlcs\n\t}\n\tif used >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - used\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) penalizeUnknownFailure(meta *routeMeta,\n\tnow time.Time) {\n\n\tr.unknownFailures++\n\tif len(meta.path) == 0 {\n\t\treturn\n\t}\n\n\tbase := 140_000.0 +\n\t\tfloat64(r.unknownFailures%8)*45_000\n\n\tvar suspect int\n\tworst := -1.0\n\tfor i, edge := range meta.path {\n\t\tr.recordLiquidityFailure(\n\t\t\tedge.key, meta.loads[i], now, base,\n\t\t)\n\n\t\tif edge.from == r.source {\n\t\t\tcontinue\n\t\t}\n\n\t\tp := channelProbability(\n\t\t\tedge.key, meta.loads[i], edge.capacity, now,\n\t\t)\n\t\trisk := -math.Log(p)\n\t\tif risk > worst {\n\t\t\tworst = risk\n\t\t\tsuspect = i\n\t\t}\n\t}\n\n\trotation := int(r.unknownFailures) % len(meta.path)\n\tif worst < 0 || r.unknownFailures%3 == 0 {\n\t\tsuspect = rotation\n\t}\n\n\tedge := meta.path[suspect]\n\tr.recordLiquidityFailure(\n\t\tedge.key, meta.loads[suspect], now, 900_000,\n\t)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\trecorded := r.reserved[edge.key]\n\t\t\tif meta.amounts[i] <= lnwire.MaxMilliSatoshi-recorded {\n\t\t\t\tr.reserved[edge.key] = recorded + meta.amounts[i]\n\t\t\t}\n\t\t\tr.recordEdgeSuccess(edge.key, meta.loads[i], now)\n\t\t\tr.policyPenalty[edge.key] *= 0.5\n\t\t}\n\n\t\tif meta.fee <= lnwire.MaxMilliSatoshi-r.committedFees {\n\t\t\tr.committedFees += meta.fee\n\t\t}\n\t\tr.successfulParts++\n\t\tr.unknownFailures = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIdx := failureEdgeIndex(rt, result.FailureSource)\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t\tr.recordEdgeSuccess(meta.path[i].key, meta.loads[i], now)\n\t}\n\n\tcode := result.Failure.Code()\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLiquidityFailure(\n\t\t\t\tedge.key, meta.loads[failIdx], now, 1_100_000,\n\t\t\t)\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.policyPenalty[edge.key] += 8_000_000\n\t\t\tif r.policyPenalty[edge.key] > maxPolicyPenalty {\n\t\t\t\tr.policyPenalty[edge.key] = maxPolicyPenalty\n\t\t\t}\n\n\t\tdefault:\n\t\t\tr.policyPenalty[edge.key] += 2_500_000\n\t\t\tif r.policyPenalty[edge.key] > maxPolicyPenalty {\n\t\t\t\tr.policyPenalty[edge.key] = maxPolicyPenalty\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.penalizeUnknownFailure(meta, now)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tfor _, edge := range meta.path {\n\t\t\tr.policyPenalty[edge.key] += 600_000\n\t\t\tif r.policyPenalty[edge.key] > maxPolicyPenalty {\n\t\t\t\tr.policyPenalty[edge.key] = maxPolicyPenalty\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\tfor i, edge := range meta.path {\n\t\t\tr.recordLiquidityFailure(\n\t\t\t\tedge.key, meta.loads[i], now, 250_000,\n\t\t\t)\n\t\t}\n\t}\n\n\tif result.FailureSource == (route.Vertex{}) &&\n\t\tr.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\n\t\tguard := meta.fee / 50\n\t\tif guard < 500 {\n\t\t\tguard = 500\n\t\t}\n\t\tif guard <= lnwire.MaxMilliSatoshi-r.budgetGuard {\n\t\t\tr.budgetGuard += guard\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 30,
"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\"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\tcandidateFinalCLTV = uint32(40)\n\tcandidateRiskPrice = 420000.0\n\tcandidateHopPrice = 8000.0\n\tcandidateReuseNats = 1.35\n\tcandidateMinShard = lnwire.MilliSatoshi(10000)\n\tcandidateProbFloor = 0.012\n\tcandidateProbCeiling = 0.995\n\tcandidatePlanBeamWidth = 10\n\tcandidateMaxFailures = uint32(64)\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 int64\n\tfeeRatePPM int64\n\tcltvDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc (e *candidateEdge) outgoingFee(amt lnwire.MilliSatoshi) int64 {\n\tfee := e.baseFeeMsat + int64(amt)*e.feeRatePPM/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn fee\n}\n\nfunc forwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif incoming == nil || outgoing == nil {\n\t\treturn 0\n\t}\n\n\toutFee := outgoing.outgoingFee(amt)\n\tinBasis := int64(amt) + outFee\n\tinFee := incoming.inboundBase +\n\t\tinBasis*incoming.inboundRate/1_000_000\n\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tsuccess uint32\n\tfailure uint32\n}\n\nvar candidateSharedBeliefs = struct {\n\tsync.Mutex\n\tm map[candidateEdgeKey]candidateBelief\n}{\n\tm: make(map[candidateEdgeKey]candidateBelief),\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn candidateProbFloor\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.045)\n\thighMode := 0.5 / (1 + math.Exp((x-0.94)/0.035))\n\tp := lowMode + highMode\n\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc candidateProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := candidatePrior(load, e.capacity)\n\n\tcandidateSharedBeliefs.Lock()\n\tbelief, ok := candidateSharedBeliefs.m[e.key]\n\tcandidateSharedBeliefs.Unlock()\n\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn candidateProbCeiling\n\t}\n\n\tif belief.upperBad > 0 && load >= belief.upperBad {\n\t\tp := prior * 0.08\n\t\tif p < candidateProbFloor {\n\t\t\tp = candidateProbFloor\n\t\t}\n\t\treturn p\n\t}\n\n\tvar learned, confidence float64\n\n\tswitch {\n\tcase belief.lowerOK > 0 && belief.upperBad > belief.lowerOK:\n\t\tmid := 0.5 * float64(belief.lowerOK+belief.upperBad)\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad-belief.lowerOK)*0.18,\n\t\t\tfloat64(e.capacity)*0.012,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp((float64(load)-mid)/width))\n\t\tconfidence = 0.88\n\n\tcase belief.upperBad > 0:\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad)*0.14,\n\t\t\tfloat64(e.capacity)*0.015,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(load)-float64(belief.upperBad))/width,\n\t\t))\n\t\tconfidence = 0.72\n\n\tcase belief.lowerOK > 0:\n\t\texcess := float64(load-belief.lowerOK) /\n\t\t\tmath.Max(float64(e.capacity), 1)\n\t\tlearned = candidateProbCeiling * math.Exp(-excess/0.12)\n\t\tconfidence = 0.62\n\n\tdefault:\n\t\treturn prior\n\t}\n\n\tp := confidence*learned + (1-confidence)*prior\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > candidateProbCeiling {\n\t\treturn candidateProbCeiling\n\t}\n\treturn p\n}\n\nfunc candidateRecordSuccess(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tb.success++\n\n\tif b.upperBad > 0 && b.lowerOK >= b.upperBad {\n\t\tb.upperBad = b.lowerOK + 1\n\t}\n\n\tcandidateSharedBeliefs.m[key] = b\n}\n\nfunc candidateRecordFailure(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif b.upperBad == 0 || load < b.upperBad {\n\t\tb.upperBad = load\n\t}\n\tb.failure++\n\n\tcandidateSharedBeliefs.m[key] = b\n}\n\ntype candidateSearchState struct {\n\tnode route.Vertex\n\tnextChanID uint64\n\tnextNode route.Vertex\n\tamountBin uint64\n}\n\ntype candidateSearchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk float64\n\thops int\n\tpath []*candidateEdge\n\tindex int\n\tstate candidateSearchState\n}\n\ntype candidateSearchQueue []*candidateSearchItem\n\nfunc (q candidateSearchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateSearchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateSearchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateSearchQueue) Push(x any) {\n\titem := x.(*candidateSearchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateSearchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tvirtual map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutes []*route.Route\n\tfees lnwire.MilliSatoshi\n\tmerit float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocal map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route]bool\n\tpenalty map[candidateEdgeKey]float64\n\tbanned map[candidateEdgeKey]bool\n\n\trouteStrikes map[uint64]uint32\n\tplan []*route.Route\n\n\tcommittedFees lnwire.MilliSatoshi\n\tfailures uint32\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[candidateEdgeKey]*candidateEdge),\n\t\tlocal: localBalances,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route]bool),\n\t\tpenalty: make(map[candidateEdgeKey]float64),\n\t\tbanned: make(map[candidateEdgeKey]bool),\n\t\trouteStrikes: make(map[uint64]uint32),\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\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\n\t\t\t\t\tbaseFeeMsat: int64(policy.FeeBaseMSat),\n\t\t\t\t\tfeeRatePPM: int64(\n\t\t\t\t\t\tpolicy.FeeProportionalMillionths,\n\t\t\t\t\t),\n\t\t\t\t\tcltvDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\tr.edges[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateAdd(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b > 0 && a > lnwire.MaxMilliSatoshi-b {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn a + b\n}\n\nfunc (r *candidateRouter) budgetLeft() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidatePathContains(path []*candidateEdge,\n\tnode route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAmountBin(amt,\n\tdeliver lnwire.MilliSatoshi) uint64 {\n\n\twidth := deliver / 64\n\tif width < 1000 {\n\t\twidth = 1000\n\t}\n\treturn uint64(amt / width)\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid shard amount\")\n\t}\n\n\tinitialState := candidateSearchState{\n\t\tnode: r.spec.Target,\n\t\tamountBin: candidateAmountBin(deliver, deliver),\n\t}\n\tdist := map[candidateSearchState]float64{\n\t\tinitialState: 0,\n\t}\n\n\tpq := &candidateSearchQueue{}\n\theap.Push(pq, &candidateSearchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t\tstate: initialState,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateSearchItem)\n\n\t\tbest, ok := dist[item.state]\n\t\tif !ok || item.score > best+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item.path, deliver)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\tif r.routeFee(rt) > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tquality := math.Exp(-item.risk)\n\t\t\tif quality < 1e-12 {\n\t\t\t\tquality = 1e-12\n\t\t\t}\n\t\t\treturn rt, quality, nil\n\t\t}\n\n\t\tvar outgoing *candidateEdge\n\t\tif len(item.path) > 0 {\n\t\t\toutgoing = item.path[0]\n\t\t}\n\n\t\tfor _, incoming := range r.incoming[item.node] {\n\t\t\tif r.banned[incoming.key] ||\n\t\t\t\tcandidatePathContains(item.path, incoming.key.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOver := item.amount\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tamountOver = candidateAdd(\n\t\t\t\t\tamountOver,\n\t\t\t\t\tforwardingFee(incoming, outgoing, item.amount),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif !incoming.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi &&\n\t\t\t\tamountOver-deliver > maxFee {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := candidateAdd(\n\t\t\t\tamountOver, r.reserved[incoming.key],\n\t\t\t)\n\t\t\tif extra != nil {\n\t\t\t\tload = candidateAdd(load, extra[incoming.key])\n\t\t\t}\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar probability float64\n\t\t\tif incoming.key.from == r.source {\n\t\t\t\tbalance, ok := r.local[incoming.key.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprobability = 0.999\n\t\t\t} else {\n\t\t\t\tprobability = candidateProbability(incoming, load)\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability)\n\t\t\treuse := 0.0\n\t\t\tif r.reserved[incoming.key] > 0 ||\n\t\t\t\t(extra != nil && extra[incoming.key] > 0) {\n\n\t\t\t\treuse = candidateReuseNats\n\t\t\t}\n\n\t\t\tfeeIncrement := float64(amountOver - item.amount)\n\t\t\tnewScore := item.score + feeIncrement +\n\t\t\t\tcandidateRiskPrice*(\n\t\t\t\t\trisk+r.penalty[incoming.key]+reuse,\n\t\t\t\t) + candidateHopPrice\n\n\t\t\tstate := candidateSearchState{\n\t\t\t\tnode: incoming.key.from,\n\t\t\t\tnextChanID: incoming.key.chanID,\n\t\t\t\tnextNode: incoming.key.to,\n\t\t\t\tamountBin: candidateAmountBin(amountOver, deliver),\n\t\t\t}\n\t\t\toldScore, exists := dist[state]\n\t\t\tif exists && oldScore <= newScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = incoming\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tdist[state] = newScore\n\t\t\theap.Push(pq, &candidateSearchItem{\n\t\t\t\tnode: incoming.key.from,\n\t\t\t\tamount: amountOver,\n\t\t\t\tscore: newScore,\n\t\t\t\trisk: item.risk + risk,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tpath: path,\n\t\t\t\tstate: state,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tdeliver lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = candidateFinalCLTV\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := forwardingFee(path[i], path[i+1], amounts[i+1])\n\t\tamounts[i] = candidateAdd(amounts[i+1], fee)\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].cltvDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := candidateFinalCLTV\n\t\tif i < n-1 {\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) forEachRouteEdge(rt *route.Route,\n\tfn func(candidateEdgeKey, lnwire.MilliSatoshi, int)) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tfn(key, amount, i)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc (r *candidateRouter) addVirtualReservation(\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\treserved[key] = candidateAdd(reserved[key], amount)\n\t})\n}\n\nfunc candidateCloneReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tclone := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tclone[key] = amount\n\t}\n\treturn clone\n}\n\nfunc (r *candidateRouter) reserveIssued(rt *route.Route) {\n\tif r.issued[rt] {\n\t\treturn\n\t}\n\n\tr.issued[rt] = true\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tr.reserved[key] = candidateAdd(r.reserved[key], amount)\n\t})\n}\n\nfunc (r *candidateRouter) releaseIssued(rt *route.Route) {\n\tif !r.issued[rt] {\n\t\treturn\n\t}\n\n\tdelete(r.issued, rt)\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tcurrent := r.reserved[key]\n\t\tif current <= amount {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = current - amount\n\t\t}\n\t})\n}\n\nfunc (r *candidateRouter) maxFeeForPlan(\n\talready lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tleft := r.budgetLeft()\n\tif left == lnwire.MaxMilliSatoshi {\n\t\treturn left\n\t}\n\tif already >= left {\n\t\treturn 0\n\t}\n\treturn left - already\n}\n\nfunc candidateAppendShard(values []lnwire.MilliSatoshi,\n\tshard, minimum, maximum lnwire.MilliSatoshi,\n) []lnwire.MilliSatoshi {\n\n\tif shard < minimum || shard > maximum {\n\t\treturn values\n\t}\n\tfor _, value := range values {\n\t\tif value == shard {\n\t\t\treturn values\n\t\t}\n\t}\n\treturn append(values, shard)\n}\n\nfunc candidateShardChoices(remaining lnwire.MilliSatoshi,\n\tleft int) []lnwire.MilliSatoshi {\n\n\tif left <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := candidateMinShard\n\tmaximum := remaining -\n\t\tcandidateMinShard*lnwire.MilliSatoshi(left-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tequal := remaining / lnwire.MilliSatoshi(left)\n\tvar choices []lnwire.MilliSatoshi\n\n\tchoices = candidateAppendShard(\n\t\tchoices, equal, minimum, maximum,\n\t)\n\tchoices = candidateAppendShard(\n\t\tchoices, equal+equal/2, minimum, maximum,\n\t)\n\tchoices = candidateAppendShard(\n\t\tchoices, equal*3/4, minimum, maximum,\n\t)\n\tchoices = candidateAppendShard(\n\t\tchoices, equal+equal/4, minimum, maximum,\n\t)\n\tchoices = candidateAppendShard(\n\t\tchoices, equal*2/3, minimum, maximum,\n\t)\n\n\treturn choices\n}\n\nfunc candidateKeepBest(states []candidatePlanState,\n\tlimit int) []candidatePlanState {\n\n\tif len(states) <= limit {\n\t\treturn states\n\t}\n\n\tfor i := 0; i < limit; i++ {\n\t\tbest := i\n\t\tfor j := i + 1; j < len(states); j++ {\n\t\t\tif states[j].merit < states[best].merit {\n\t\t\t\tbest = j\n\t\t\t}\n\t\t}\n\t\tstates[i], states[best] = states[best], states[i]\n\t}\n\n\treturn states[:limit]\n}\n\nfunc (r *candidateRouter) tryPlan(amount lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, bool) {\n\n\tif parts < 1 ||\n\t\tamount < candidateMinShard*lnwire.MilliSatoshi(parts) {\n\n\t\treturn nil, false\n\t}\n\n\tstates := []candidatePlanState{{\n\t\tremaining: amount,\n\t\tvirtual: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}}\n\n\tfor index := 0; index < parts; index++ {\n\t\tleft := parts - index\n\t\tnext := make([]candidatePlanState, 0, len(states)*3)\n\n\t\tfor _, state := range states {\n\t\t\tfor _, shard := range candidateShardChoices(\n\t\t\t\tstate.remaining, left,\n\t\t\t) {\n\t\t\t\tmaxFee := r.maxFeeForPlan(state.fees)\n\t\t\t\trt, quality, err := r.findRoute(\n\t\t\t\t\tshard, state.virtual, maxFee,\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\tfee := r.routeFee(rt)\n\t\t\t\tbudget := r.budgetLeft()\n\t\t\t\tif budget != lnwire.MaxMilliSatoshi &&\n\t\t\t\t\tstate.fees+fee > budget {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvirtual := candidateCloneReservations(\n\t\t\t\t\tstate.virtual,\n\t\t\t\t)\n\t\t\t\tr.addVirtualReservation(virtual, rt)\n\n\t\t\t\troutes := make([]*route.Route, len(state.routes)+1)\n\t\t\t\tcopy(routes, state.routes)\n\t\t\t\troutes[len(state.routes)] = rt\n\n\t\t\t\tif quality < 1e-12 {\n\t\t\t\t\tquality = 1e-12\n\t\t\t\t}\n\n\t\t\t\tnext = append(next, candidatePlanState{\n\t\t\t\t\tremaining: state.remaining -\n\t\t\t\t\t\tr.routeAmount(rt),\n\t\t\t\t\tvirtual: virtual,\n\t\t\t\t\troutes: routes,\n\t\t\t\t\tfees: state.fees + fee,\n\t\t\t\t\tmerit: state.merit + float64(fee) +\n\t\t\t\t\t\tcandidateRiskPrice*\n\t\t\t\t\t\t\t(-math.Log(quality)),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(next) == 0 {\n\t\t\treturn nil, false\n\t\t}\n\t\tstates = candidateKeepBest(next, candidatePlanBeamWidth)\n\t}\n\n\tvar best *candidatePlanState\n\tfor i := range states {\n\t\tif states[i].remaining != 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif best == nil || states[i].merit < best.merit {\n\t\t\tbest = &states[i]\n\t\t}\n\t}\n\tif best == nil {\n\t\treturn nil, false\n\t}\n\n\treturn best.routes, true\n}\n\nfunc (r *candidateRouter) makePlan(amount lnwire.MilliSatoshi,\n\tslots uint32) ([]*route.Route, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tfull, quality, fullErr := r.findRoute(\n\t\tamount, nil, r.budgetLeft(),\n\t)\n\n\tif slots == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tpreferred := 1\n\tswitch {\n\tcase fullErr != nil:\n\t\tpreferred = 2\n\n\tcase quality < 0.08:\n\t\tpreferred = 3\n\n\tcase quality < 0.35 && amount >= 20_000_000:\n\t\tpreferred = 2\n\t}\n\n\tif r.failures > 0 && preferred < 2 {\n\t\tpreferred = 2\n\t}\n\tif r.failures >= 3 && preferred < 3 {\n\t\tpreferred = 3\n\t}\n\tif preferred > int(slots) {\n\t\tpreferred = int(slots)\n\t}\n\n\tif preferred == 1 && fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tfor parts := preferred; parts <= int(slots); parts++ {\n\t\tplan, ok := r.tryPlan(amount, parts)\n\t\tif ok {\n\t\t\treturn plan, nil\n\t\t}\n\t}\n\n\tif fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tshard := amount / lnwire.MilliSatoshi(slots)\n\tif shard < candidateMinShard {\n\t\tshard = candidateMinShard\n\t}\n\n\tfor shard >= candidateMinShard {\n\t\trt, _, err := r.findRoute(\n\t\t\tshard, nil, r.budgetLeft(),\n\t\t)\n\t\tif err == nil {\n\t\t\treturn []*route.Route{rt}, nil\n\t\t}\n\n\t\tif shard == candidateMinShard {\n\t\t\tbreak\n\t\t}\n\t\tshard = shard * 2 / 3\n\t\tif shard < candidateMinShard {\n\t\t\tshard = candidateMinShard\n\t\t}\n\t}\n\n\treturn nil, fullErr\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\thash := uint64(1469598103934665603)\n\tmix := func(value uint64) {\n\t\thash ^= value\n\t\thash *= 1099511628211\n\t}\n\n\tmix(uint64(len(rt.Hops)))\n\tmix(uint64(rt.TotalAmount))\n\tfor _, hop := range rt.Hops {\n\t\tmix(hop.ChannelID)\n\t\tmix(uint64(hop.AmtToForward))\n\t}\n\n\treturn hash\n}\n\nfunc (r *candidateRouter) penalizeRepeatedRoute(\n\trt *route.Route, strike uint32) {\n\n\tif len(rt.Hops) == 0 {\n\t\treturn\n\t}\n\n\tindex := int(strike % uint32(len(rt.Hops)))\n\tkey, _, ok := r.routeEdgeAt(rt, index)\n\tif !ok {\n\t\treturn\n\t}\n\n\tr.penalty[key] += 0.55 + 0.08*float64(strike)\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif r.failures >= candidateMaxFailures {\n\t\treturn nil, errors.New(\"routing failure budget exhausted\")\n\t}\n\n\tfor replans := 0; replans < 12; replans++ {\n\t\tif len(r.plan) == 0 {\n\t\t\tmaxParts := r.spec.MaxParts\n\t\t\tif maxParts == 0 {\n\t\t\t\tmaxParts = 1\n\t\t\t}\n\t\t\tif inFlightHtlcs >= maxParts {\n\t\t\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t\t\t}\n\n\t\t\tplan, err := r.makePlan(\n\t\t\t\tamt, maxParts-inFlightHtlcs,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr.plan = plan\n\t\t}\n\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tif r.routeAmount(rt) > amt {\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tbudget := r.budgetLeft()\n\t\tif budget != lnwire.MaxMilliSatoshi && fee > budget {\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tstrikes := r.routeStrikes[hash]\n\t\tif strikes >= 3 && replans < 11 {\n\t\t\tr.penalizeRepeatedRoute(rt, strikes)\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tif strikes >= 12 {\n\t\t\treturn nil, errors.New(\"no untried route found\")\n\t\t}\n\n\t\tr.reserveIssued(rt)\n\t\tr.attempts++\n\t\treturn rt, nil\n\t}\n\n\treturn nil, errors.New(\"unable to construct a distinct route\")\n}\n\nfunc (r *candidateRouter) routeFailureIndex(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) routeEdgeAt(rt *route.Route,\n\tindex int) (candidateEdgeKey, lnwire.MilliSatoshi, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tamount := rt.TotalAmount\n\tif index > 0 {\n\t\tamount = rt.Hops[index-1].AmtToForward\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, amount, true\n}\n\nfunc (r *candidateRouter) observedLoad(key candidateEdgeKey,\n\tfallback lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif load := r.reserved[key]; load > 0 {\n\t\treturn load\n\t}\n\treturn fallback\n}\n\nfunc (r *candidateRouter) weakestRouteEdge(\n\trt *route.Route) (candidateEdgeKey, lnwire.MilliSatoshi, bool) {\n\n\tvar weakest candidateEdgeKey\n\tvar weakestAmount lnwire.MilliSatoshi\n\tweakestProbability := 2.0\n\tfound := false\n\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn\n\t\t}\n\n\t\tload := r.observedLoad(key, amount)\n\t\tprobability := candidateProbability(edge, load)\n\t\tif key.from == r.source {\n\t\t\tprobability = 0.998\n\t\t}\n\n\t\tif !found || probability < weakestProbability {\n\t\t\tfound = true\n\t\t\tweakest = key\n\t\t\tweakestAmount = load\n\t\t\tweakestProbability = probability\n\t\t}\n\t})\n\n\treturn weakest, weakestAmount, found\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\thash := candidateRouteHash(rt)\n\n\tif result.Failure == nil {\n\t\tdelete(r.routeStrikes, hash)\n\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\t\tcandidateRecordSuccess(\n\t\t\t\tkey, r.observedLoad(key, amount),\n\t\t\t)\n\t\t\tif r.penalty[key] > 0.30 {\n\t\t\t\tr.penalty[key] -= 0.30\n\t\t\t} else {\n\t\t\t\tdelete(r.penalty, key)\n\t\t\t}\n\t\t})\n\n\t\tr.committedFees = candidateAdd(\n\t\t\tr.committedFees, r.routeFee(rt),\n\t\t)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tr.routeStrikes[hash]++\n\tr.plan = nil\n\n\tfailIndex := r.routeFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tfor i := 0; i < failIndex && i < len(rt.Hops); i++ {\n\t\tkey, amount, ok := r.routeEdgeAt(rt, i)\n\t\tif ok {\n\t\t\tcandidateRecordSuccess(\n\t\t\t\tkey, r.observedLoad(key, amount),\n\t\t\t)\n\t\t}\n\t}\n\n\tcode := result.Failure.Code()\n\tif key, amount, ok := r.routeEdgeAt(rt, failIndex); ok {\n\t\tload := r.observedLoad(key, amount)\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tcandidateRecordFailure(key, load)\n\t\t\tr.penalty[key] += 1.65\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.banned[key] = true\n\n\t\tdefault:\n\t\t\tr.penalty[key] += 0.85\n\t\t}\n\t} else {\n\t\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\t\tkey, load, ok := r.weakestRouteEdge(rt)\n\t\t\tif ok {\n\t\t\t\tcandidateRecordFailure(key, load)\n\t\t\t\tr.penalty[key] += 1.85\n\t\t\t}\n\t\t}\n\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\t_ lnwire.MilliSatoshi, _ int) {\n\n\t\t\tr.penalty[key] += 0.06\n\t\t})\n\n\t\tr.penalizeRepeatedRoute(\n\t\t\trt, r.routeStrikes[hash],\n\t\t)\n\t}\n\n\tr.releaseIssued(rt)\n\treturn nil\n}"
}
},
{
"id": 31,
"parent": 3,
"score": 0.2183,
"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\"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\n\triskPriceMsat = 420000.0\n\thopPriceMsat = 8000.0\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tforward lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif out == nil {\n\t\treturn 0\n\t}\n\n\toutFee := int64(out.fee(forward))\n\tinboundBasis := int64(forward) + outFee\n\tinboundFee := in.inboundBase +\n\t\tinboundBasis*in.inboundRate/1_000_000\n\n\ttotal := outFee + inboundFee\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n\tepoch uint64\n}\n\nvar sharedKnowledge = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[edgeKey]liquidityBelief\n}{\n\tbeliefs: make(map[edgeKey]liquidityBelief),\n}\n\ntype localEdgeState struct {\n\tbelief liquidityBelief\n\tfailures int\n\tlastFail lnwire.MilliSatoshi\n\tpolicyBad bool\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tstate map[edgeKey]*localEdgeState\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tdiversion map[edgeKey]int\n\tattempted map[uint64]int\n\treported map[*route.Route]bool\n\tcommittedFee lnwire.MilliSatoshi\n\n\tfailureStreak int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.epoch++\n\tepoch := sharedKnowledge.epoch\n\tsharedKnowledge.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tstate: make(map[edgeKey]*localEdgeState),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tdiversion: make(map[edgeKey]int),\n\t\tattempted: make(map[uint64]int),\n\t\treported: make(map[*route.Route]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\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\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: int64(\n\t\t\t\t\t\tch.InboundFee.BaseFee,\n\t\t\t\t\t),\n\t\t\t\t\tinboundRate: int64(\n\t\t\t\t\t\tch.InboundFee.FeeRate,\n\t\t\t\t\t),\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[edge.to] = append(\n\t\t\t\t\tr.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\tsharedKnowledge.Lock()\n\tfor key, edge := range r.edgeByKey {\n\t\tbelief, ok := sharedKnowledge.beliefs[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\n\t\tr.state[key] = &localEdgeState{\n\t\t\tbelief: belief,\n\t\t}\n\t}\n\tsharedKnowledge.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.025)\n\thighMode := 0.5 /\n\t\t(1 + math.Exp((x-0.90)/0.035))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif load > e.capacity {\n\t\treturn 0.005\n\t}\n\n\tif e.from == r.source {\n\t\tif load <= r.localBalances[e.chanID] {\n\t\t\treturn 0.995\n\t\t}\n\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(load, e.capacity)\n\tstate := r.state[e.key]\n\tif state == nil || state.belief.conf <= 0 {\n\t\treturn prior\n\t}\n\n\tbelief := state.belief\n\tvar age uint64\n\tif r.epoch > belief.epoch {\n\t\tage = r.epoch - belief.epoch\n\t}\n\n\tconf := belief.conf * math.Exp(-float64(age)/8)\n\tif conf < 0.025 {\n\t\treturn prior\n\t}\n\n\tactiveBounds := age <= 4\n\tvar learned float64\n\n\tswitch {\n\tcase activeBounds && belief.lowerOK > 0 &&\n\t\tload <= belief.lowerOK:\n\n\t\tlearned = 0.995\n\n\tcase activeBounds && belief.upperFail > 0 &&\n\t\tload >= belief.upperFail:\n\n\t\tlearned = 0.015\n\n\tdefault:\n\t\testimate := belief.estimate\n\t\tif estimate <= 0 {\n\t\t\testimate = e.capacity / 2\n\t\t}\n\n\t\twidth := float64(e.capacity) * 0.07\n\t\tif width < 1_000_000 {\n\t\t\twidth = 1_000_000\n\t\t}\n\n\t\tz := (float64(load) - float64(estimate)) / width\n\t\tlearned = clampProbability(1 / (1 + math.Exp(z)))\n\t}\n\n\treturn clampProbability(\n\t\tconf*learned + (1-conf)*prior,\n\t)\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFee >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - r.committedFee\n}\n\nfunc (r *candidateRouter) shardFeeBudget(shard, remaining,\n\tpartsLeft lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tbudget := r.remainingFeeBudget()\n\tif budget == lnwire.MaxMilliSatoshi ||\n\t\tshard >= remaining {\n\n\t\treturn budget\n\t}\n\tif budget <= 0 || remaining <= 0 {\n\t\treturn 0\n\t}\n\n\tshare := budget * shard / remaining\n\n\t// Give a shard a small allowance for base fees while retaining most\n\t// of the budget for the remaining parts.\n\theadroom := budget / 20\n\tif partsLeft > 1 {\n\t\theadroom /= partsLeft\n\t}\n\tif share > budget-headroom {\n\t\treturn budget\n\t}\n\n\treturn share + headroom\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tcost float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].cost < q[j].cost\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchItem))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\n\treturn item\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tfee lnwire.MilliSatoshi\n\tlogProb float64\n\tscore float64\n}\n\nfunc (r *candidateRouter) edgePenalty(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tpenalty := float64(r.diversion[e.key]) * 300000\n\n\treserved := r.reserved[e.key]\n\tif reserved > 0 {\n\t\tpenalty += 1_600_000\n\t\tif e.capacity > 0 {\n\t\t\tpenalty += 900000 *\n\t\t\t\tfloat64(reserved) / float64(e.capacity)\n\t\t}\n\t}\n\n\tstate := r.state[e.key]\n\tif state == nil || state.failures == 0 {\n\t\treturn penalty\n\t}\n\n\tfailures := state.failures\n\tif failures > 6 {\n\t\tfailures = 6\n\t}\n\n\tunit := 1_150_000.0\n\tif state.lastFail > 0 &&\n\t\tload*5 < state.lastFail*4 {\n\n\t\tunit = 120000\n\t}\n\n\treturn penalty + float64(failures)*unit\n}\n\nfunc (r *candidateRouter) search(amt lnwire.MilliSatoshi,\n\triskScale float64, avoidReserved bool) (*route.Route,\n\t*routeMeta, error) {\n\n\tbest := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbest[r.spec.Target] = 0\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tcost: 0,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\n\t\tcurrentBest, ok := best[item.node]\n\t\tif !ok || item.cost > currentBest+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tout := next[item.node]\n\t\tfor _, in := range r.incomingEdges[item.node] {\n\t\t\tstate := r.state[in.key]\n\t\t\tif state != nil && state.policyBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[in.key]\n\t\t\tif avoidReserved && reserved > 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tfee = nodeFee(in, out, item.amt)\n\t\t\t}\n\t\t\tif fee > lnwire.MaxMilliSatoshi-item.amt {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOver := item.amt + fee\n\t\t\tif !in.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif reserved > in.capacity ||\n\t\t\t\tamountOver > in.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := amountOver + reserved\n\t\t\tif in.from == r.source {\n\t\t\t\tbalance := r.localBalances[in.chanID]\n\t\t\t\tif reserved > balance ||\n\t\t\t\t\tamountOver > balance-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(in, load)\n\t\t\tstep := float64(fee) -\n\t\t\t\triskScale*math.Log(probability) +\n\t\t\t\thopPriceMsat\n\t\t\tstep += r.edgePenalty(in, load)\n\n\t\t\tnewCost := item.cost + step\n\t\t\toldCost, seen := best[in.from]\n\t\t\tif seen && newCost >= oldCost {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[in.from] = newCost\n\t\t\tnext[in.from] = in\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: in.from,\n\t\t\t\tcost: newCost,\n\t\t\t\tamt: amountOver,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := best[r.source]; !ok {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfee := rt.TotalAmount - amt\n\tlogProb := 0.0\n\tfor i, edge := range path {\n\t\tamountOver := routeAmountAt(rt, i)\n\t\tload := amountOver + r.reserved[edge.key]\n\t\tlogProb += math.Log(\n\t\t\tr.edgeProbability(edge, load),\n\t\t)\n\t}\n\n\treturn rt, &routeMeta{\n\t\tpath: path,\n\t\tfee: fee,\n\t\tlogProb: logProb,\n\t}, nil\n}\n\nfunc (r *candidateRouter) findRoute(amt,\n\tfeeCap lnwire.MilliSatoshi) (*route.Route, *routeMeta, error) {\n\n\tbaseScale := riskPriceMsat\n\tif feeCap != lnwire.MaxMilliSatoshi {\n\t\tbudgetScale := float64(feeCap) * 0.45\n\t\tif budgetScale < 3500 {\n\t\t\tbudgetScale = 3500\n\t\t}\n\t\tif budgetScale < baseScale {\n\t\t\tbaseScale = budgetScale\n\t\t}\n\t}\n\n\tscales := []float64{\n\t\tbaseScale,\n\t\tbaseScale / 4,\n\t\t0,\n\t\triskPriceMsat,\n\t\triskPriceMsat * 2,\n\t}\n\n\tvar bestRoute *route.Route\n\tvar bestMeta *routeMeta\n\n\tfor pass := 0; pass < 2; pass++ {\n\t\tavoidReserved := pass == 0\n\n\t\tfor _, scale := range scales {\n\t\t\trt, meta, err := r.search(\n\t\t\t\tamt, scale, avoidReserved,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\tmeta.fee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmeta.score = float64(meta.fee) -\n\t\t\t\tbaseScale*meta.logProb +\n\t\t\t\tfloat64(len(meta.path))*hopPriceMsat\n\n\t\t\tif bestMeta == nil ||\n\t\t\t\tmeta.score < bestMeta.score {\n\n\t\t\t\tbestRoute = rt\n\t\t\t\tbestMeta = meta\n\t\t\t}\n\t\t}\n\n\t\tif bestRoute != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\treturn bestRoute, bestMeta, nil\n}\n\nfunc (r *candidateRouter) buildRoute(\n\tdeliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tpath := make([]*candidateEdge, 0, 12)\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, nil, errors.New(\"routing 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, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tn := len(path)\n\tif n == 0 {\n\t\treturn nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamountOver := make([]lnwire.MilliSatoshi, n)\n\texpiryOver := make([]uint32, n)\n\n\tamountOver[n-1] = deliver\n\texpiryOver[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := nodeFee(\n\t\t\tpath[i], path[i+1], amountOver[i+1],\n\t\t)\n\t\tamountOver[i] = amountOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\n\t\tif i < n-1 {\n\t\t\tforward = amountOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amountOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, path, nil\n}\n\nfunc ceilDiv(a lnwire.MilliSatoshi,\n\tb int) lnwire.MilliSatoshi {\n\n\tif b <= 1 {\n\t\treturn a\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(b)\n\treturn (a + divisor - 1) / divisor\n}\n\nfunc appendShardCandidate(candidates []lnwire.MilliSatoshi,\n\tshard, remaining lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif shard <= 0 {\n\t\treturn candidates\n\t}\n\tif shard > remaining {\n\t\tshard = remaining\n\t}\n\n\tfor _, existing := range candidates {\n\t\tif existing == shard {\n\t\t\treturn candidates\n\t\t}\n\t}\n\n\treturn append(candidates, shard)\n}\n\nfunc shardCandidates(remaining lnwire.MilliSatoshi,\n\tpartsLeft, failureStreak int) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminShard := ceilDiv(remaining, partsLeft)\n\tcandidates := make([]lnwire.MilliSatoshi, 0, 9)\n\n\tcandidates = appendShardCandidate(\n\t\tcandidates, remaining, remaining,\n\t)\n\tcandidates = appendShardCandidate(\n\t\tcandidates, remaining*3/4, remaining,\n\t)\n\tcandidates = appendShardCandidate(\n\t\tcandidates, remaining/2, remaining,\n\t)\n\tcandidates = appendShardCandidate(\n\t\tcandidates, minShard*9/4, remaining,\n\t)\n\tcandidates = appendShardCandidate(\n\t\tcandidates, minShard*3/2, remaining,\n\t)\n\tcandidates = appendShardCandidate(\n\t\tcandidates, minShard, remaining,\n\t)\n\n\tif failureStreak > 0 {\n\t\tcandidates = appendShardCandidate(\n\t\t\tcandidates, minShard*3/4, remaining,\n\t\t)\n\t\tcandidates = appendShardCandidate(\n\t\t\tcandidates, minShard/2, remaining,\n\t\t)\n\t}\n\n\tfor i := 0; i < len(candidates); i++ {\n\t\tfor j := i + 1; j < len(candidates); j++ {\n\t\t\tif candidates[j] > candidates[i] {\n\t\t\t\tcandidates[i], candidates[j] =\n\t\t\t\t\tcandidates[j], candidates[i]\n\t\t\t}\n\t\t}\n\t}\n\n\treturn candidates\n}\n\nfunc routeFingerprint(rt *route.Route) uint64 {\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\thash ^= value\n\t\thash *= prime\n\t}\n\n\tmix(uint64(rt.TotalAmount))\n\tfor _, hop := range rt.Hops {\n\t\tmix(hop.ChannelID)\n\t\tmix(uint64(hop.AmtToForward))\n\t}\n\n\treturn hash\n}\n\nfunc (r *candidateRouter) selectShard(remaining,\n\tpartsLeft lnwire.MilliSatoshi, relaxedBudget bool) (\n\t*route.Route, *routeMeta, error) {\n\n\tpartCount := int(partsLeft)\n\tminShard := ceilDiv(remaining, partCount)\n\tcandidates := shardCandidates(\n\t\tremaining, partCount, r.failureStreak,\n\t)\n\n\tvar bestRoute *route.Route\n\tvar bestMeta *routeMeta\n\tbestValue := math.Inf(-1)\n\n\tfor _, shard := range candidates {\n\t\tfeeCap := r.shardFeeBudget(\n\t\t\tshard, remaining, partsLeft,\n\t\t)\n\t\tif relaxedBudget {\n\t\t\tfeeCap = r.remainingFeeBudget()\n\t\t}\n\n\t\trt, meta, err := r.findRoute(shard, feeCap)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvalue := math.Log(float64(shard)) +\n\t\t\t0.70*meta.logProb -\n\t\t\tfloat64(meta.fee)/riskPriceMsat -\n\t\t\tfloat64(len(meta.path))*0.025\n\n\t\tif shard < minShard {\n\t\t\tvalue -= 0.85\n\t\t}\n\n\t\tif bestMeta == nil || value > bestValue {\n\t\t\tbestRoute = rt\n\t\t\tbestMeta = meta\n\t\t\tbestValue = value\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\treturn bestRoute, bestMeta, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(\n\tamt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment complete\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar lastErr error\n\n\tfor diversify := 0; diversify < 4; diversify++ {\n\t\trt, meta, err := r.selectShard(\n\t\t\tamt, lnwire.MilliSatoshi(partsLeft), false,\n\t\t)\n\t\tif err != nil {\n\t\t\trt, meta, err = r.selectShard(\n\t\t\t\tamt, lnwire.MilliSatoshi(partsLeft), true,\n\t\t\t)\n\t\t}\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tbreak\n\t\t}\n\n\t\tfingerprint := routeFingerprint(rt)\n\t\tif r.attempted[fingerprint] == 0 ||\n\t\t\tdiversify == 3 {\n\n\t\t\tr.attempted[fingerprint]++\n\t\t\treturn rt, nil\n\t\t}\n\n\t\t// An exact route-and-amount retry is usually wasted in a\n\t\t// bimodal network. Penalize its complete corridor and search\n\t\t// again, while still allowing it if no alternative exists.\n\t\tfor _, edge := range meta.path {\n\t\t\tr.diversion[edge.key]++\n\t\t}\n\t\tlastErr = errors.New(\"route already attempted\")\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\n}\n\nfunc routeAmountAt(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 routeEdgeKey(rt *route.Route, index int) edgeKey {\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn edgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tbelief liquidityBelief) {\n\n\tif state := r.state[key]; state != nil {\n\t\tstate.belief = belief\n\t}\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.beliefs[key] = belief\n\tsharedKnowledge.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tstate := r.state[key]\n\tif edge == nil || state == nil {\n\t\treturn\n\t}\n\n\tbelief := state.belief\n\tif load > belief.lowerOK {\n\t\tbelief.lowerOK = load\n\t}\n\n\testimate := edge.capacity * 88 / 100\n\tif load > estimate {\n\t\testimate = load\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tif belief.upperFail > 0 &&\n\t\tload >= belief.upperFail {\n\n\t\tbelief.upperFail = 0\n\t}\n\n\tbelief.conf = 0.92\n\tbelief.epoch = r.epoch\n\n\tif state.lastFail == 0 || load >= state.lastFail {\n\t\tstate.failures = 0\n\t\tstate.lastFail = 0\n\t} else if state.failures > 0 {\n\t\tstate.failures--\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tstate := r.state[key]\n\tif edge == nil || state == nil {\n\t\treturn\n\t}\n\n\tif state.failures < 8 {\n\t\tstate.failures++\n\t}\n\tstate.lastFail = load\n\n\tbelief := state.belief\n\tif belief.upperFail == 0 ||\n\t\tload < belief.upperFail {\n\n\t\tbelief.upperFail = load\n\t}\n\n\testimate := load * 18 / 100\n\tif belief.estimate == 0 ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tif belief.lowerOK >= load {\n\t\tbelief.lowerOK = load * 2 / 3\n\t\tbelief.conf = 0.64\n\t} else {\n\t\tbelief.conf = 0.90\n\t}\n\n\tif edge.from == r.source {\n\t\tbelief.conf *= 0.75\n\t}\n\n\tbelief.epoch = r.epoch\n\tr.saveBelief(key, belief)\n}\n\nfunc 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) recordPassedPrefix(\n\trt *route.Route, stop int) {\n\n\tif stop > len(rt.Hops) {\n\t\tstop = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < stop; i++ {\n\t\tkey := routeEdgeKey(rt, i)\n\t\tload := routeAmountAt(rt, i) +\n\t\t\tr.reserved[key]\n\t\tr.recordSuccess(key, load)\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif rt == nil || r.reported[rt] {\n\t\treturn nil\n\t}\n\tr.reported[rt] = true\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tamountOver := routeAmountAt(rt, i)\n\t\t\tload := amountOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, load)\n\t\t\tr.reserved[key] += amountOver\n\t\t}\n\n\t\tdelivered := rt.Hops[len(rt.Hops)-1].\n\t\t\tAmtToForward\n\t\tif rt.TotalAmount > delivered {\n\t\t\tr.committedFee +=\n\t\t\t\trt.TotalAmount - delivered\n\t\t}\n\n\t\tr.failureStreak = 0\n\t\treturn nil\n\t}\n\n\tfailureType := fmt.Sprintf(\"%T\", result.Failure)\n\tisLiquidity := strings.Contains(\n\t\tfailureType, \"TemporaryChannelFailure\",\n\t)\n\tisPolicy := strings.Contains(\n\t\tfailureType, \"FeeInsufficient\",\n\t) || strings.Contains(\n\t\tfailureType, \"IncorrectCltvExpiry\",\n\t)\n\n\tfailAt := failureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tif failAt >= 0 && failAt < len(rt.Hops) {\n\t\t// Every edge before the failing node carried the HTLC. This\n\t\t// positive evidence is particularly valuable in a bimodal\n\t\t// liquidity distribution.\n\t\tif isLiquidity || isPolicy {\n\t\t\tr.recordPassedPrefix(rt, failAt)\n\t\t}\n\n\t\tkey := routeEdgeKey(rt, failAt)\n\t\tload := routeAmountAt(rt, failAt) +\n\t\t\tr.reserved[key]\n\n\t\tswitch {\n\t\tcase isLiquidity:\n\t\t\tr.recordFailure(key, load)\n\t\t\tr.failureStreak++\n\n\t\tcase isPolicy:\n\t\t\tif state := r.state[key]; state != nil {\n\t\t\t\tstate.policyBad = true\n\t\t\t\tstate.failures = 8\n\t\t\t\tstate.lastFail = load\n\t\t\t}\n\t\t\tr.failureStreak++\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif isLiquidity {\n\t\t// The source is unknown, so diversify without manufacturing a\n\t\t// liquidity bound for every edge.\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tstate := r.state[key]\n\t\t\tif state == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif state.failures < 6 {\n\t\t\t\tstate.failures++\n\t\t\t}\n\t\t\tstate.lastFail = routeAmountAt(rt, i) +\n\t\t\t\tr.reserved[key]\n\t\t\tr.diversion[key]++\n\t\t}\n\n\t\tr.failureStreak++\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 32,
"parent": 4,
"score": 0.3885,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\n\thopCostMsat = float64(25_000)\n\treusedEdgeCostMsat = float64(1_150_000)\n\textraPartCostMsat = float64(50_000)\n\n\tmaxSearchHops = 48\n\tmaxLabelsPerNode = 10\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 40\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amt <= 0 {\n\t\treturn e.baseFeeMsat\n\t}\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinboundBasis := int64(amt) + outbound\n\n\tinbound := int64(in.inboundBase) +\n\t\tinboundBasis*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tpos := float64(b.upperBad-amt) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\n\treturn price\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tvar cutoff lnwire.MilliSatoshi\n\tswitch {\n\tcase r.localFails[key] >= 3:\n\t\tcutoff = upper / 2\n\tcase r.localFails[key] == 2:\n\t\tcutoff = upper * 2 / 3\n\tdefault:\n\t\tcutoff = upper * 4 / 5\n\t}\n\n\tif cutoff <= 0 {\n\t\treturn 1\n\t}\n\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.995\n\t\t\t\tif r.localUpper[edge.key] > 0 {\n\t\t\t\t\tp = 0.80\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reserved > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.55\n\t\t\t\t}\n\n\t\t\t\tloadFraction := float64(totalLoad) /\n\t\t\t\t\tmath.Max(1, float64(edge.capacity))\n\n\t\t\t\tscore += reuse * (1 + loadFraction)\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc proportionalAmount(total lnwire.MilliSatoshi,\n\tweight, sum int64) lnwire.MilliSatoshi {\n\n\tif total <= 0 || weight <= 0 || sum <= 0 {\n\t\treturn 0\n\t}\n\n\tq := total / lnwire.MilliSatoshi(sum)\n\trem := total % lnwire.MilliSatoshi(sum)\n\n\treturn q*lnwire.MilliSatoshi(weight) +\n\t\trem*lnwire.MilliSatoshi(weight)/lnwire.MilliSatoshi(sum)\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tif weight > 0 {\n\t\t\tsum += weight\n\t\t}\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tremainingWeight := sum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tif weight <= 0 || remainingWeight <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tpart := proportionalAmount(remaining, weight, remainingWeight)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - 1\n\t\t}\n\t\tif part <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tremainingWeight -= weight\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil\n\t}\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{40, 60}, {60, 40},\n\t\t\t{25, 75}, {75, 25},\n\t\t\t{15, 85}, {85, 15},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{20, 30, 50},\n\t\t\t{50, 30, 20},\n\t\t\t{15, 25, 60},\n\t\t\t{60, 25, 15},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 4:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{10, 15, 25, 50},\n\t\t\t{50, 25, 15, 10},\n\t\t\t{15, 20, 25, 40},\n\t\t\t{40, 25, 20, 15},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tascending := make([]int64, parts)\n\t\tdescending := make([]int64, parts)\n\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tascending[i] = int64(i + 1)\n\t\t\tdescending[i] = int64(parts - i)\n\t\t}\n\n\t\tpatterns = append(\n\t\t\tpatterns,\n\t\t\tweightedAllocation(total, ascending),\n\t\t\tweightedAllocation(total, descending),\n\t\t)\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, remainingBudget)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\n\treturn plan, totalScore, true\n}\n\ntype amountFraction struct {\n\tnum int64\n\tden int64\n}\n\nfunc amountProbes(limit lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\tfractions := []amountFraction{\n\t\t{1, 1},\n\t\t{7, 8},\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\t{1, 4},\n\t\t{1, 5},\n\t\t{1, 6},\n\t\t{1, 8},\n\t\t{1, 10},\n\t\t{1, 12},\n\t\t{1, 16},\n\t\t{1, 24},\n\t\t{1, 32},\n\t\t{1, 48},\n\t\t{1, 64},\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]struct{})\n\tresult := make([]lnwire.MilliSatoshi, 0, len(fractions)+1)\n\n\tfor _, fraction := range fractions {\n\t\tamt := proportionalAmount(limit, fraction.num, fraction.den)\n\t\tif amt <= 0 {\n\t\t\tamt = 1\n\t\t}\n\t\tif amt > limit {\n\t\t\tamt = limit\n\t\t}\n\t\tif _, ok := seen[amt]; ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tseen[amt] = struct{}{}\n\t\tresult = append(result, amt)\n\t}\n\n\tif _, ok := seen[1]; !ok {\n\t\tresult = append(result, 1)\n\t}\n\n\tsort.Slice(result, func(i, j int) bool {\n\t\treturn result[i] > result[j]\n\t})\n\n\treturn result\n}\n\nfunc (r *candidateRouter) findLargestRoute(\n\tlimit lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) (*plannedRoute,\n\tlnwire.MilliSatoshi, error) {\n\n\tif limit <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid shard limit\")\n\t}\n\n\tvar failedAbove lnwire.MilliSatoshi\n\n\tfor _, probe := range amountProbes(limit) {\n\t\tpr, err := r.findRoute(probe, reservations, feeBudget)\n\t\tif err != nil {\n\t\t\tfailedAbove = probe\n\t\t\tcontinue\n\t\t}\n\n\t\tbest := pr\n\t\tbestAmt := probe\n\n\t\tif failedAbove > probe+1 {\n\t\t\tlow := probe\n\t\t\thigh := failedAbove - 1\n\n\t\t\tfor iteration := 0; iteration < 5 && low < high; iteration++ {\n\t\t\t\tmid := low + (high-low+1)/2\n\t\t\t\tcandidate, findErr := r.findRoute(\n\t\t\t\t\tmid, reservations, feeBudget,\n\t\t\t\t)\n\t\t\t\tif findErr != nil {\n\t\t\t\t\thigh = mid - 1\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tlow = mid\n\t\t\t\tbestAmt = mid\n\t\t\t\tbest = candidate\n\t\t\t}\n\t\t}\n\n\t\treturn best, bestAmt, nil\n\t}\n\n\treturn nil, 0, errors.New(\"no shard route found\")\n}\n\nfunc (r *candidateRouter) tryGreedyPlan(total lnwire.MilliSatoshi,\n\tpartLimit int, firstFraction amountFraction,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\tif total <= 0 || partLimit <= 0 {\n\t\treturn nil, 0, false\n\t}\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremaining := total\n\tplan := make([]*plannedRoute, 0, partLimit)\n\ttotalScore := float64(0)\n\n\tfor part := 0; part < partLimit && remaining > 0; part++ {\n\t\tpartsLeft := partLimit - part\n\n\t\tif partsLeft == 1 {\n\t\t\tpr, err := r.findRoute(\n\t\t\t\tremaining, reservations, remainingBudget,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\n\t\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\t\treturn nil, 0, false\n\t\t\t\t}\n\t\t\t\tremainingBudget -= pr.meta.fee\n\t\t\t}\n\n\t\t\tplan = append(plan, pr)\n\t\t\ttotalScore += pr.score\n\t\t\taddReservationSet(reservations, pr.meta)\n\t\t\tremaining = 0\n\t\t\tbreak\n\t\t}\n\n\t\tmaxLimit := remaining -\n\t\t\tlnwire.MilliSatoshi(partsLeft-1)\n\t\tif maxLimit <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tmaxRoute, maxAmount, err := r.findLargestRoute(\n\t\t\tmaxLimit, reservations, remainingBudget,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tshard := maxAmount\n\t\tif part == 0 && firstFraction.den > 0 {\n\t\t\tscaled := proportionalAmount(\n\t\t\t\tmaxAmount,\n\t\t\t\tfirstFraction.num,\n\t\t\t\tfirstFraction.den,\n\t\t\t)\n\t\t\tif scaled > 0 && scaled < shard {\n\t\t\t\tshard = scaled\n\t\t\t}\n\t\t}\n\n\t\tvar pr *plannedRoute\n\t\tif shard == maxAmount {\n\t\t\tpr = maxRoute\n\t\t} else {\n\t\t\tpr, err = r.findRoute(\n\t\t\t\tshard, reservations, remainingBudget,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tpr = maxRoute\n\t\t\t\tshard = maxAmount\n\t\t\t}\n\t\t}\n\n\t\tif shard <= 0 || shard >= remaining {\n\t\t\tif shard == remaining {\n\t\t\t\tplan = append(plan, pr)\n\t\t\t\ttotalScore += pr.score\n\t\t\t\tremaining = 0\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremaining -= shard\n\t}\n\n\tif remaining != 0 || len(plan) == 0 {\n\t\treturn nil, 0, false\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tgreedyFractions := []amountFraction{\n\t\t{1, 1},\n\t\t{4, 5},\n\t\t{2, 3},\n\t\t{1, 2},\n\t}\n\n\tfor parts := 2; parts <= int(slots); parts++ {\n\t\tfor _, fraction := range greedyFractions {\n\t\t\tplan, score, ok := r.tryGreedyPlan(\n\t\t\t\ttotal, parts, fraction, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn best, nil\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 {\n\t\tif queuedAmount(r.plan) != amt ||\n\t\t\tuint32(len(r.plan)) > slots {\n\n\t\t\tr.plan = nil\n\t\t}\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.30\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\tif failIdx < 0 &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure &&\n\t\tlen(meta.path) > 0 {\n\n\t\tfailIdx = 0\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t325_000 + float64(count)*200_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_000_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 650_000\n\t}\n\n\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\tremaining := r.remainingFeeBudget()\n\t\tif remaining != lnwire.MaxMilliSatoshi &&\n\t\t\tremaining > 0 &&\n\t\t\tmeta.fee*20 >= remaining*19 {\n\n\t\t\tguard := meta.fee / 25\n\t\t\tif guard < 1_000 {\n\t\t\t\tguard = 1_000\n\t\t\t}\n\t\t\tif guard > remaining {\n\t\t\t\tguard = remaining\n\t\t\t}\n\n\t\t\tr.budgetGuard += guard\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 33,
"parent": 3,
"score": 0.3204,
"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\"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\triskPriceMsat = 420000.0\n\thopPriceMsat = 7000.0\n\tpartPriceMsat = 12000.0\n\tmaxRouterAttempts = 36\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc nodeFee(in, out *candidateEdge,\n\tforward lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif out == nil {\n\t\treturn 0\n\t}\n\n\toutFee := int64(out.fee(forward))\n\tbasis := int64(forward) + outFee\n\tinFee := in.inboundBase + basis*in.inboundRate/1_000_000\n\ttotal := outFee + inFee\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n\tepoch uint64\n}\n\nvar sharedKnowledge = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[edgeKey]liquidityBelief\n}{\n\tbeliefs: make(map[edgeKey]liquidityBelief),\n}\n\ntype localEdgeState struct {\n\tbelief liquidityBelief\n\tfailures int\n\tlastFail lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tstate map[edgeKey]*localEdgeState\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tblocked map[edgeKey]bool\n\tcommittedFee lnwire.MilliSatoshi\n\treported map[*route.Route]bool\n\n\trouteFailures map[string]int\n\tamountFailures map[string]int\n\tfailureStreak int\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.epoch++\n\tepoch := sharedKnowledge.epoch\n\tsharedKnowledge.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tstate: make(map[edgeKey]*localEdgeState),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tblocked: make(map[edgeKey]bool),\n\t\treported: make(map[*route.Route]bool),\n\t\trouteFailures: make(map[string]int),\n\t\tamountFailures: make(map[string]int),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t}\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\te := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: 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\tinboundBase: int64(\n\t\t\t\t\t\tch.InboundFee.BaseFee,\n\t\t\t\t\t),\n\t\t\t\t\tinboundRate: int64(\n\t\t\t\t\t\tch.InboundFee.FeeRate,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = e\n\t\t\t\tr.incomingEdges[e.to] = append(\n\t\t\t\t\tr.incomingEdges[e.to], e,\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedKnowledge.Lock()\n\tfor key, e := range r.edgeByKey {\n\t\tb, ok := sharedKnowledge.beliefs[key]\n\t\tif !ok {\n\t\t\tb.estimate = e.capacity / 2\n\t\t}\n\t\tr.state[key] = &localEdgeState{belief: b}\n\t}\n\tsharedKnowledge.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.025)\n\thighMode := 0.5 / (1 + math.Exp((x-0.90)/0.035))\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tif load > e.capacity {\n\t\treturn 0.005\n\t}\n\n\tst := r.state[e.key]\n\tif e.from == r.source {\n\t\tif load > r.localBalances[e.chanID] {\n\t\t\treturn 0.005\n\t\t}\n\t\tif st == nil || st.belief.conf <= 0 ||\n\t\t\tst.belief.upperFail == 0 ||\n\t\t\tload < st.belief.upperFail {\n\n\t\t\treturn 0.995\n\t\t}\n\n\t\tage := uint64(0)\n\t\tif r.epoch > st.belief.epoch {\n\t\t\tage = r.epoch - st.belief.epoch\n\t\t}\n\t\tconf := st.belief.conf * math.Exp(-float64(age)/5)\n\t\treturn clampProbability(\n\t\t\tconf*0.025 + (1-conf)*0.995,\n\t\t)\n\t}\n\n\tprior := bimodalPrior(load, e.capacity)\n\tif st == nil || st.belief.conf <= 0 {\n\t\treturn prior\n\t}\n\n\tb := st.belief\n\tage := uint64(0)\n\tif r.epoch > b.epoch {\n\t\tage = r.epoch - b.epoch\n\t}\n\n\tconf := b.conf * math.Exp(-float64(age)/14)\n\tif conf < 0.025 {\n\t\treturn prior\n\t}\n\n\tactiveBounds := age <= 10\n\tvar learned float64\n\n\tswitch {\n\tcase activeBounds && b.lowerOK > 0 && load <= b.lowerOK:\n\t\tlearned = 0.995\n\n\tcase activeBounds && b.upperFail > 0 && load >= b.upperFail:\n\t\tlearned = 0.015\n\n\tdefault:\n\t\testimate := b.estimate\n\t\tif estimate <= 0 {\n\t\t\testimate = e.capacity / 2\n\t\t}\n\n\t\twidth := float64(e.capacity) * 0.07\n\t\tif width < 1_000_000 {\n\t\t\twidth = 1_000_000\n\t\t}\n\t\tz := (float64(load) - float64(estimate)) / width\n\t\tlearned = clampProbability(1 / (1 + math.Exp(z)))\n\t}\n\n\treturn clampProbability(conf*learned + (1-conf)*prior)\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFee >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFee\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tcost float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].cost < q[j].cost\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchItem))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tfee lnwire.MilliSatoshi\n\tlogProb float64\n\tscore float64\n\tid string\n}\n\nfunc (r *candidateRouter) edgePenalty(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tpenalty := 0.0\n\treserved := r.reserved[e.key]\n\tif reserved > 0 {\n\t\tpenalty += 1_400_000\n\t\tif e.capacity > 0 {\n\t\t\tpenalty += 900_000 *\n\t\t\t\tfloat64(reserved) / float64(e.capacity)\n\t\t}\n\t}\n\n\tst := r.state[e.key]\n\tif st == nil || st.failures == 0 {\n\t\treturn penalty\n\t}\n\n\tunit := 900_000.0\n\tif st.lastFail > 0 && load*2 < st.lastFail {\n\t\tunit = 100_000\n\t} else if st.lastFail > 0 && load*4 < st.lastFail*3 {\n\t\tunit = 300_000\n\t}\n\n\treturn penalty + float64(st.failures)*unit\n}\n\nfunc (r *candidateRouter) search(amt lnwire.MilliSatoshi,\n\triskScale float64) (*route.Route, *routeMeta, error) {\n\n\tbest := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tbest[r.spec.Target] = 0\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tcost: 0,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tcurrentBest, ok := best[item.node]\n\t\tif !ok || item.cost > currentBest+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tout := next[item.node]\n\t\tfor _, in := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[in.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tfee = nodeFee(in, out, item.amt)\n\t\t\t}\n\t\t\tif fee > lnwire.MaxMilliSatoshi-item.amt {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := item.amt + fee\n\t\t\tif !in.usable(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[in.key]\n\t\t\tif reserved > in.capacity ||\n\t\t\t\tover > in.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := over + reserved\n\t\t\tif in.from == r.source {\n\t\t\t\tbalance := r.localBalances[in.chanID]\n\t\t\t\tif reserved > balance ||\n\t\t\t\t\tover > balance-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(in, load)\n\t\t\tstep := float64(fee) -\n\t\t\t\triskScale*math.Log(p) + hopPriceMsat\n\t\t\tstep += r.edgePenalty(in, load)\n\t\t\tnewCost := item.cost + step\n\n\t\t\told, seen := best[in.from]\n\t\t\tif seen && newCost >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[in.from] = newCost\n\t\t\tnext[in.from] = in\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: in.from,\n\t\t\t\tcost: newCost,\n\t\t\t\tamt: over,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := best[r.source]; !ok {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfee := rt.TotalAmount - amt\n\tlogProb := 0.0\n\tfor i, e := range path {\n\t\tover := routeAmountAt(rt, i)\n\t\tload := over + r.reserved[e.key]\n\t\tlogProb += math.Log(r.edgeProbability(e, load))\n\t}\n\n\treturn rt, &routeMeta{\n\t\tpath: path,\n\t\tfee: fee,\n\t\tlogProb: logProb,\n\t\tid: routeID(rt),\n\t}, nil\n}\n\nfunc (r *candidateRouter) findRoute(amt,\n\tfeeCap lnwire.MilliSatoshi) (*route.Route, *routeMeta, error) {\n\n\tscales := []float64{\n\t\triskPriceMsat,\n\t\triskPriceMsat / 3,\n\t\t0,\n\t\triskPriceMsat * 2,\n\t}\n\n\tvar bestRoute *route.Route\n\tvar bestMeta *routeMeta\n\tseen := make(map[string]bool)\n\n\tfor _, scale := range scales {\n\t\trt, meta, err := r.search(amt, scale)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\tmeta.fee > feeCap {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif seen[meta.id] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[meta.id] = true\n\n\t\tmeta.score = float64(meta.fee) -\n\t\t\triskPriceMsat*meta.logProb +\n\t\t\tfloat64(len(meta.path))*hopPriceMsat\n\n\t\tif bestMeta == nil || meta.score < bestMeta.score {\n\t\t\tbestRoute = rt\n\t\t\tbestMeta = meta\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, nil, errors.New(\"no route found\")\n\t}\n\n\treturn bestRoute, bestMeta, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tpath := make([]*candidateEdge, 0, 12)\n\tvisited := make(map[route.Vertex]bool)\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, nil, errors.New(\"routing cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\te, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, e)\n\t\tnode = e.to\n\t}\n\n\tn := len(path)\n\tif n == 0 {\n\t\treturn nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, n)\n\texpiryOver := make([]uint32, n)\n\tamtOver[n-1] = deliver\n\texpiryOver[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := nodeFee(path[i], path[i+1], amtOver[i+1])\n\t\tif fee > lnwire.MaxMilliSatoshi-amtOver[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, e := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < n-1 {\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: e.to,\n\t\t\tChannelID: e.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, path, nil\n}\n\nfunc ceilDiv(a lnwire.MilliSatoshi, b int) lnwire.MilliSatoshi {\n\tif b <= 1 {\n\t\treturn a\n\t}\n\n\td := lnwire.MilliSatoshi(b)\n\treturn a/d + func() lnwire.MilliSatoshi {\n\t\tif a%d != 0 {\n\t\t\treturn 1\n\t\t}\n\t\treturn 0\n\t}()\n}\n\nfunc numParts(remaining, shard lnwire.MilliSatoshi) int {\n\tif shard <= 0 {\n\t\treturn 1\n\t}\n\tn := int(remaining / shard)\n\tif remaining%shard != 0 {\n\t\tn++\n\t}\n\tif n < 1 {\n\t\treturn 1\n\t}\n\treturn n\n}\n\nfunc candidateShardAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft int) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminShard := ceilDiv(amt, partsLeft)\n\tvalues := make(map[lnwire.MilliSatoshi]bool)\n\tadd := func(v lnwire.MilliSatoshi) {\n\t\tif v < minShard {\n\t\t\tv = minShard\n\t\t}\n\t\tif v > amt {\n\t\t\tv = amt\n\t\t}\n\t\tif v > 0 {\n\t\t\tvalues[v] = true\n\t\t}\n\t}\n\n\tadd(amt)\n\tfractions := [][2]int{\n\t\t{7, 8},\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\tfor _, fraction := range fractions {\n\t\tadd(amt * lnwire.MilliSatoshi(fraction[0]) /\n\t\t\tlnwire.MilliSatoshi(fraction[1]))\n\t}\n\tfor parts := 2; parts <= partsLeft; parts++ {\n\t\tadd(ceilDiv(amt, parts))\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, 0, len(values))\n\tfor value := range values {\n\t\tresult = append(result, value)\n\t}\n\tsort.Slice(result, func(i, j int) bool {\n\t\treturn result[i] > result[j]\n\t})\n\n\treturn result\n}\n\nfunc routeID(rt *route.Route) string {\n\tif rt == nil {\n\t\treturn \"\"\n\t}\n\n\tvar b strings.Builder\n\tfor _, hop := range rt.Hops {\n\t\tb.WriteString(strconv.FormatUint(hop.ChannelID, 10))\n\t\tb.WriteByte('/')\n\t}\n\treturn b.String()\n}\n\nfunc amountFailureID(rt *route.Route) string {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn \"\"\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn routeID(rt) + \"#\" +\n\t\tstrconv.FormatInt(int64(delivered), 10)\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 complete\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpartsLeft := int(r.spec.MaxParts - inFlightHtlcs)\n\tif partsLeft < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tamounts := candidateShardAmounts(amt, partsLeft)\n\n\tvar bestRoute *route.Route\n\tvar bestMeta *routeMeta\n\tbestScore := math.Inf(1)\n\tvar lastErr error\n\n\tfor _, shard := range amounts {\n\t\tplannedParts := numParts(amt, shard)\n\t\tif plannedParts > partsLeft {\n\t\t\tcontinue\n\t\t}\n\n\t\trt, meta, err := r.findRoute(shard, feeBudget)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tfailureID := amountFailureID(rt)\n\t\texactFailures := r.amountFailures[failureID]\n\t\tif exactFailures >= 4 {\n\t\t\tcontinue\n\t\t}\n\n\t\tplanScore := float64(plannedParts) *\n\t\t\t(meta.score + partPriceMsat)\n\n\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\tprojectedFee := float64(meta.fee) *\n\t\t\t\tfloat64(plannedParts)\n\t\t\tif projectedFee > float64(feeBudget) {\n\t\t\t\tplanScore += 6 *\n\t\t\t\t\t(projectedFee - float64(feeBudget))\n\t\t\t}\n\t\t}\n\n\t\tplanScore += float64(r.routeFailures[meta.id]) *\n\t\t\t1_100_000\n\t\tplanScore += float64(exactFailures) * 1_800_000\n\n\t\tif r.failureStreak > 0 && r.lastFailedAmt > 0 &&\n\t\t\tshard*5 >= r.lastFailedAmt*4 {\n\n\t\t\tplanScore += float64(r.failureStreak) * 1_500_000\n\t\t}\n\n\t\tif planScore < bestScore {\n\t\t\tbestScore = planScore\n\t\t\tbestRoute = rt\n\t\t\tbestMeta = meta\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\t_ = bestMeta\n\tr.attempts++\n\treturn bestRoute, nil\n}\n\nfunc routeAmountAt(rt *route.Route, index int) lnwire.MilliSatoshi {\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc routeEdgeKey(rt *route.Route, index int) edgeKey {\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\treturn edgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tif st := r.state[key]; st != nil {\n\t\tst.belief = b\n\t}\n\n\tsharedKnowledge.Lock()\n\tsharedKnowledge.beliefs[key] = b\n\tsharedKnowledge.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\te := r.edgeByKey[key]\n\tst := r.state[key]\n\tif e == nil || st == nil {\n\t\treturn\n\t}\n\n\tb := st.belief\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\n\thighEstimate := e.capacity * 88 / 100\n\tif load > highEstimate {\n\t\thighEstimate = load\n\t}\n\tif highEstimate > b.estimate {\n\t\tb.estimate = highEstimate\n\t}\n\tif b.upperFail > 0 && load >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\n\tb.conf = 0.92\n\tb.epoch = r.epoch\n\tst.failures = 0\n\tst.lastFail = 0\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) recordFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\te := r.edgeByKey[key]\n\tst := r.state[key]\n\tif e == nil || st == nil {\n\t\treturn\n\t}\n\n\tst.failures++\n\tst.lastFail = load\n\n\tb := st.belief\n\tif b.upperFail == 0 || load < b.upperFail {\n\t\tb.upperFail = load\n\t}\n\n\tdepletedEstimate := load / 5\n\tif b.estimate == 0 || depletedEstimate < b.estimate {\n\t\tb.estimate = depletedEstimate\n\t}\n\n\tif b.lowerOK >= load {\n\t\tb.lowerOK = load * 2 / 3\n\t\tb.conf = 0.65\n\t} else {\n\t\tb.conf = 0.90\n\t}\n\tif e.from == r.source {\n\t\tb.conf = 0.72\n\t}\n\n\tb.epoch = r.epoch\n\tr.saveBelief(key, b)\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\tindex := i + 1\n\t\t\tif index >= len(rt.Hops) {\n\t\t\t\treturn len(rt.Hops) - 1\n\t\t\t}\n\t\t\treturn index\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) penalizeWholeRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey := routeEdgeKey(rt, i)\n\t\tif st := r.state[key]; st != nil {\n\t\t\tst.failures++\n\t\t\tst.lastFail = routeAmountAt(rt, i) +\n\t\t\t\tr.reserved[key]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif rt == nil || r.reported[rt] {\n\t\treturn nil\n\t}\n\tr.reported[rt] = true\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := routeEdgeKey(rt, i)\n\t\t\tover := routeAmountAt(rt, i)\n\t\t\tload := over + r.reserved[key]\n\t\t\tr.recordSuccess(key, load)\n\t\t\tr.reserved[key] += over\n\t\t}\n\n\t\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\tif rt.TotalAmount > delivered {\n\t\t\tr.committedFee += rt.TotalAmount - delivered\n\t\t}\n\n\t\tr.failureStreak = 0\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tid := routeID(rt)\n\tr.routeFailures[id]++\n\tr.amountFailures[amountFailureID(rt)]++\n\n\tif len(rt.Hops) > 0 {\n\t\tr.lastFailedAmt =\n\t\t\trt.Hops[len(rt.Hops)-1].AmtToForward\n\t}\n\tr.failureStreak++\n\n\tkind := fmt.Sprintf(\"%T\", result.Failure)\n\tisLiquidity := strings.Contains(\n\t\tkind, \"TemporaryChannelFailure\",\n\t)\n\tisPolicy := strings.Contains(kind, \"FeeInsufficient\") ||\n\t\tstrings.Contains(kind, \"IncorrectCltvExpiry\")\n\n\tfailIndex := failureEdgeIndex(rt, result.FailureSource)\n\tif failIndex >= 0 && failIndex < len(rt.Hops) {\n\t\tkey := routeEdgeKey(rt, failIndex)\n\t\tload := routeAmountAt(rt, failIndex) +\n\t\t\tr.reserved[key]\n\n\t\tswitch {\n\t\tcase isLiquidity:\n\t\t\tr.recordFailure(key, load)\n\n\t\tcase isPolicy:\n\t\t\tr.blocked[key] = true\n\t\t\tif st := r.state[key]; st != nil {\n\t\t\t\tst.failures += 4\n\t\t\t\tst.lastFail = load\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif st := r.state[key]; st != nil {\n\t\t\t\tst.failures += 2\n\t\t\t\tst.lastFail = load\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tr.penalizeWholeRoute(rt)\n\treturn nil\n}"
}
},
{
"id": 34,
"parent": 4,
"score": 0.2579,
"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\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskPriceMsat = float64(420_000)\n\thopCostMsat = float64(20_000)\n\treusedEdgeCostMsat = float64(950_000)\n\textraPartCostMsat = float64(70_000)\n\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 10\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 64\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amt <= 0 {\n\t\treturn e.baseFeeMsat\n\t}\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\n// nodeFee computes the fee charged by the node between in and out. The\n// inbound fee is assessed on the forwarded amount plus the node's outbound\n// fee. The combined inbound and outbound fee is floored at zero.\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinboundAmt := int64(amt)\n\tif outbound > 0 && inboundAmt <= math.MaxInt64-outbound {\n\t\tinboundAmt += outbound\n\t}\n\n\tinbound := int64(in.inboundBase) +\n\t\tinboundAmt*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// reserved is the cumulative load from successful shards of this\n\t// payment. It represents either held atomic shards or liquidity already\n\t// moved by settled non-atomic shards.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\t// localUpper is payment-local evidence. Unlike shared beliefs, it is\n\t// used to force a material amount reduction immediately after failure.\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\t// Evidence remains useful across nearby payments, while gradually\n\t// becoming soft enough for a moving network to contradict it.\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\t// Failure bounds are deliberately soft. A concurrent shard or\n\t\t// another payment may have caused a temporary depletion.\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tcutoff := upper * 3 / 4\n\tif r.localFails[key] >= 2 {\n\t\tcutoff = upper * 3 / 5\n\t}\n\tif r.localFails[key] >= 4 {\n\t\tcutoff = upper / 2\n\t}\n\tif cutoff <= 0 {\n\t\tcutoff = 1\n\t}\n\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\tif feeCap == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tnow := r.view.Now()\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\n\t\t\tif edge.from == r.source {\n\t\t\t\t// The local snapshot plus our reservation ledger makes the\n\t\t\t\t// first hop much more certain than an unknown remote edge.\n\t\t\t\tp = 0.995\n\t\t\t\tif r.localUpper[edge.key] > 0 {\n\t\t\t\t\tp = 0.80\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\triskPriceMsat*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reserved > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.55\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tif len(weights) == 0 || total < lnwire.MilliSatoshi(len(weights)) {\n\t\treturn nil\n\t}\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tif weight <= 0 {\n\t\t\treturn nil\n\t\t}\n\t\tsum += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tremainingWeight := sum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tpart := remaining *\n\t\t\tlnwire.MilliSatoshi(weight) /\n\t\t\tlnwire.MilliSatoshi(remainingWeight)\n\n\t\tminTail := lnwire.MilliSatoshi(len(weights) - i - 1)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part > remaining-minTail {\n\t\t\tpart = remaining - minTail\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tremainingWeight -= weight\n\t}\n\n\treturn result\n}\n\nfunc sameAllocation(a, b []lnwire.MilliSatoshi) 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\n\treturn true\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil\n\t}\n\n\tvar patterns [][]lnwire.MilliSatoshi\n\tadd := func(weights []int64) {\n\t\tallocation := weightedAllocation(total, weights)\n\t\tif allocation == nil {\n\t\t\treturn\n\t\t}\n\t\tfor _, old := range patterns {\n\t\t\tif sameAllocation(old, allocation) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tpatterns = append(patterns, allocation)\n\t}\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\tadd(equal)\n\n\tif parts == 1 {\n\t\treturn patterns\n\t}\n\n\tascending := make([]int64, parts)\n\tdescending := make([]int64, parts)\n\tfor i := 0; i < parts; i++ {\n\t\tascending[i] = int64(i + 1)\n\t\tdescending[i] = int64(parts - i)\n\t}\n\tadd(ascending)\n\tadd(descending)\n\n\t// Rotate one dominant shard through the plan. This captures unequal\n\t// parallel corridors and, importantly, tries both cheap-first and\n\t// expensive-first fee-budget orderings.\n\tfor _, dominant := range []int64{3, 6} {\n\t\tfor pos := 0; pos < parts; pos++ {\n\t\t\tweights := make([]int64, parts)\n\t\t\tfor i := range weights {\n\t\t\t\tweights[i] = 1\n\t\t\t}\n\t\t\tweights[pos] = dominant\n\t\t\tadd(weights)\n\t\t}\n\t}\n\n\t// Geometric shapes cover public maximum-HTLC ceilings that do not line\n\t// up with equal halves.\n\tif parts <= 6 {\n\t\tfor shift := 0; shift < parts; shift++ {\n\t\t\tweights := make([]int64, parts)\n\t\t\tweight := int64(1)\n\t\t\tfor i := 0; i < parts; i++ {\n\t\t\t\tweights[(i+shift)%parts] = weight\n\t\t\t\tif weight < 8 {\n\t\t\t\t\tweight *= 2\n\t\t\t\t}\n\t\t\t}\n\t\t\tadd(weights)\n\t\t}\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi,\n\treserveBudget bool) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremainingAmount := lnwire.MilliSatoshi(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tshardCap := remainingBudget\n\n\t\t// Try both exact flexible accounting and a proportional mode. The\n\t\t// former permits unequal base fees; the latter prevents an early\n\t\t// shard from consuming a budget needed to complete the route set.\n\t\tif reserveBudget &&\n\t\t\tremainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\tremainingAmount > shard {\n\n\t\t\tshardCap = remainingBudget * shard / remainingAmount\n\t\t\tallowance := remainingBudget / 20\n\t\t\tif allowance > 25_000 {\n\t\t\t\tallowance = 25_000\n\t\t\t}\n\t\t\tif shardCap <= remainingBudget-allowance {\n\t\t\t\tshardCap += allowance\n\t\t\t} else {\n\t\t\t\tshardCap = remainingBudget\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, shardCap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\n\treturn plan, totalScore, true\n}\n\nfunc addShardCandidate(dst *[]lnwire.MilliSatoshi,\n\tamt, total lnwire.MilliSatoshi) {\n\n\tif amt <= 0 || amt >= total {\n\t\treturn\n\t}\n\tfor _, old := range *dst {\n\t\tif old == amt {\n\t\t\treturn\n\t\t}\n\t}\n\n\t*dst = append(*dst, amt)\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tslots uint32, feeBudget lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif slots <= 1 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tvar candidates []lnwire.MilliSatoshi\n\taddShardCandidate(&candidates, total*7/8, total)\n\taddShardCandidate(&candidates, total*3/4, total)\n\taddShardCandidate(&candidates, total*2/3, total)\n\taddShardCandidate(&candidates, total/2, total)\n\n\taverage := (total + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\taddShardCandidate(&candidates, average, total)\n\taddShardCandidate(&candidates, average*3/4, total)\n\taddShardCandidate(&candidates, average/2, total)\n\n\tfor shard := total / 3; shard >= 10_000; shard = shard * 2 / 3 {\n\t\taddShardCandidate(&candidates, shard, total)\n\t\tnext := shard * 2 / 3\n\t\tif next >= shard {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tsort.Slice(candidates, func(i, j int) bool {\n\t\treturn candidates[i] > candidates[j]\n\t})\n\n\tvar best *plannedRoute\n\tbestUtility := math.Inf(1)\n\n\tfor _, shard := range candidates {\n\t\tpr, err := r.findRoute(shard, r.reserved, feeBudget)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Prefer progress when an exact route set is unavailable, while\n\t\t// retaining enough cost pressure to reject an extreme detour.\n\t\tprogressCredit := 0.00035 * float64(shard)\n\t\tutility := pr.score - progressCredit\n\t\tif utility < bestUtility {\n\t\t\tbest = pr\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tif feeBudget == 0 {\n\t\treturn nil, errors.New(\"fee budget exhausted\")\n\t}\n\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tmaxParts := int(slots)\n\tif total < lnwire.MilliSatoshi(maxParts) {\n\t\tmaxParts = int(total)\n\t}\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tfor _, reserveBudget := range []bool{false, true} {\n\t\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\t\tallocation, feeBudget, reserveBudget,\n\t\t\t\t)\n\t\t\t\tif ok && score < bestScore {\n\t\t\t\t\tbest = plan\n\t\t\t\t\tbestScore = score\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\treturn best, nil\n\t}\n\n\tpr, err := r.fallbackShard(total, slots, feeBudget)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn []*plannedRoute{pr}, nil\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t// A forwarding node reports a failure of its outgoing edge.\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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.30\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t300_000 + float64(count)*175_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\t// These indicate a deterministic mismatch with the advertised\n\t\t\t// outgoing policy for this payment.\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 8_000_000\n\n\t\t\t// A fee failure can also involve the inbound policy on the\n\t\t\t// preceding edge. Penalize that edge without permanently\n\t\t\t// blocking it, since it may work with another outgoing edge.\n\t\t\tif code == lnwire.CodeFeeInsufficient && failIdx > 0 {\n\t\t\t\tprev := meta.path[failIdx-1]\n\t\t\t\tr.edgePenalty[prev.key] += 2_000_000\n\t\t\t}\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_000_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// An unknown failure source may be a dispatch refusal or another\n\t// failure that never established which channel lacked liquidity. Do\n\t// not invent a hard liquidity bound for the first edge.\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 650_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 35,
"parent": 6,
"score": 0.2235,
"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\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskPriceMsat = float64(420_000)\n\thopCostMsat = float64(70_000)\n\treusedEdgeCostMsat = float64(1_200_000)\n\textraPartCostMsat = float64(55_000)\n\n\tmaxSearchHops = 40\n\tmaxLabelsPerNode = 18\n\tmaxRouterAttempts = 72\n\tmaxConsecutiveMisses = 64\n\tmaxBoundRelaxations = 3\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amt <= 0 {\n\t\treturn e.baseFeeMsat\n\t}\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinboundBasis := int64(amt) + outbound\n\tinbound := int64(in.inboundBase) +\n\t\tinboundBasis*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\tlocalUpper map[edgeKey]lnwire.MilliSatoshi\n\tlocalFails map[edgeKey]int\n\tblocked map[edgeKey]bool\n\tedgePenalty map[edgeKey]float64\n\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\n\tattempts int\n\tconsecutiveMisses int\n\tboundRelaxations int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalUpper: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[edgeKey]int),\n\t\tblocked: make(map[edgeKey]bool),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tissued: make(map[*route.Route]*routeMeta),\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\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tpos := float64(b.upperBad-amt) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tminFeeIdx := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[minFeeIdx].fee {\n\t\t\t\tminFeeIdx = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == minFeeIdx {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\n\treturn dst\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) retryCutoff(key edgeKey) lnwire.MilliSatoshi {\n\tupper := r.localUpper[key]\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tvar cutoff lnwire.MilliSatoshi\n\tswitch {\n\tcase r.localFails[key] >= 3:\n\t\tcutoff = upper / 3\n\tcase r.localFails[key] == 2:\n\t\tcutoff = upper / 2\n\tdefault:\n\t\tcutoff = upper * 2 / 3\n\t}\n\n\tif cutoff <= 0 {\n\t\treturn 1\n\t}\n\n\treturn cutoff\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.blocked[edge.key] || containsNode(cur, edge.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad < over || totalLoad > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcutoff := r.retryCutoff(edge.key)\n\t\t\tif cutoff > 0 && totalLoad >= cutoff {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.995\n\t\t\t\tif r.localUpper[edge.key] > 0 {\n\t\t\t\t\tp = 0.80\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := cur.score +\n\t\t\t\tfloat64(nodeCharge) +\n\t\t\t\triskPriceMsat*(-math.Log(p)) +\n\t\t\t\thopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reserved > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.55\n\t\t\t\t}\n\n\t\t\t\tloadFraction := float64(totalLoad) /\n\t\t\t\t\tmath.Max(1, float64(edge.capacity))\n\t\t\t\tscore += reuse * (1 + loadFraction)\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\tloads[i] = amounts[i] + reservations[edge.key]\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc proportionalAmount(total lnwire.MilliSatoshi,\n\tweight, sum int64) lnwire.MilliSatoshi {\n\n\tif total <= 0 || weight <= 0 || sum <= 0 {\n\t\treturn 0\n\t}\n\n\tq := total / lnwire.MilliSatoshi(sum)\n\trem := total % lnwire.MilliSatoshi(sum)\n\n\treturn q*lnwire.MilliSatoshi(weight) +\n\t\trem*lnwire.MilliSatoshi(weight)/lnwire.MilliSatoshi(sum)\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tvar sum int64\n\tfor _, weight := range weights {\n\t\tif weight > 0 {\n\t\t\tsum += weight\n\t\t}\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tremainingWeight := sum\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\t\tif weight <= 0 || remainingWeight <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tpart := proportionalAmount(remaining, weight, remainingWeight)\n\t\tif part <= 0 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part >= remaining {\n\t\t\tpart = remaining - 1\n\t\t}\n\t\tif part <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tremainingWeight -= weight\n\t}\n\n\treturn result\n}\n\nfunc allocationPatterns(total lnwire.MilliSatoshi,\n\tparts int) [][]lnwire.MilliSatoshi {\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil\n\t}\n\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\n\tpatterns := [][]lnwire.MilliSatoshi{\n\t\tweightedAllocation(total, equal),\n\t}\n\n\tswitch parts {\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{40, 60}, {60, 40},\n\t\t\t{30, 70}, {70, 30},\n\t\t\t{20, 80}, {80, 20},\n\t\t\t{10, 90}, {90, 10},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 3:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{20, 30, 50},\n\t\t\t{50, 30, 20},\n\t\t\t{15, 25, 60},\n\t\t\t{60, 25, 15},\n\t\t\t{10, 30, 60},\n\t\t\t{60, 30, 10},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tcase 4:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{10, 15, 25, 50},\n\t\t\t{50, 25, 15, 10},\n\t\t\t{15, 20, 25, 40},\n\t\t\t{40, 25, 20, 15},\n\t\t\t{10, 20, 30, 40},\n\t\t\t{40, 30, 20, 10},\n\t\t} {\n\t\t\tpatterns = append(\n\t\t\t\tpatterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tascending := make([]int64, parts)\n\t\tdescending := make([]int64, parts)\n\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tascending[i] = int64(i + 1)\n\t\t\tdescending[i] = int64(parts - i)\n\t\t}\n\n\t\tpatterns = append(\n\t\t\tpatterns,\n\t\t\tweightedAllocation(total, ascending),\n\t\t\tweightedAllocation(total, descending),\n\t\t)\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, remainingBudget)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\treturn plan, totalScore, true\n}\n\ntype amountFraction struct {\n\tnum int64\n\tden int64\n}\n\nfunc amountProbes(limit lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\tfractions := []amountFraction{\n\t\t{1, 1},\n\t\t{7, 8},\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\t{1, 4},\n\t\t{1, 5},\n\t\t{1, 6},\n\t\t{1, 8},\n\t\t{1, 10},\n\t\t{1, 12},\n\t\t{1, 16},\n\t\t{1, 24},\n\t\t{1, 32},\n\t\t{1, 48},\n\t\t{1, 64},\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]struct{})\n\tresult := make([]lnwire.MilliSatoshi, 0, len(fractions)+1)\n\n\tfor _, fraction := range fractions {\n\t\tamt := proportionalAmount(limit, fraction.num, fraction.den)\n\t\tif amt <= 0 {\n\t\t\tamt = 1\n\t\t}\n\t\tif amt > limit {\n\t\t\tamt = limit\n\t\t}\n\t\tif _, ok := seen[amt]; ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tseen[amt] = struct{}{}\n\t\tresult = append(result, amt)\n\t}\n\n\tif _, ok := seen[1]; !ok {\n\t\tresult = append(result, 1)\n\t}\n\n\tsort.Slice(result, func(i, j int) bool {\n\t\treturn result[i] > result[j]\n\t})\n\n\treturn result\n}\n\nfunc (r *candidateRouter) findLargestRoute(\n\tlimit lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) (*plannedRoute,\n\tlnwire.MilliSatoshi, error) {\n\n\tif limit <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid shard limit\")\n\t}\n\n\tvar failedAbove lnwire.MilliSatoshi\n\n\tfor _, probe := range amountProbes(limit) {\n\t\tpr, err := r.findRoute(probe, reservations, feeBudget)\n\t\tif err != nil {\n\t\t\tfailedAbove = probe\n\t\t\tcontinue\n\t\t}\n\n\t\tbest := pr\n\t\tbestAmt := probe\n\n\t\tif failedAbove > probe+1 {\n\t\t\tlow := probe\n\t\t\thigh := failedAbove - 1\n\n\t\t\tfor iteration := 0; iteration < 6 && low < high; iteration++ {\n\t\t\t\tmid := low + (high-low+1)/2\n\t\t\t\tcandidate, findErr := r.findRoute(\n\t\t\t\t\tmid, reservations, feeBudget,\n\t\t\t\t)\n\t\t\t\tif findErr != nil {\n\t\t\t\t\thigh = mid - 1\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tlow = mid\n\t\t\t\tbestAmt = mid\n\t\t\t\tbest = candidate\n\t\t\t}\n\t\t}\n\n\t\treturn best, bestAmt, nil\n\t}\n\n\treturn nil, 0, errors.New(\"no shard route found\")\n}\n\nfunc (r *candidateRouter) tryGreedyPlan(total lnwire.MilliSatoshi,\n\tpartLimit int, firstFraction amountFraction,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\tif total <= 0 || partLimit <= 0 {\n\t\treturn nil, 0, false\n\t}\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tremaining := total\n\tplan := make([]*plannedRoute, 0, partLimit)\n\ttotalScore := float64(0)\n\n\tfor part := 0; part < partLimit && remaining > 0; part++ {\n\t\tpartsLeft := partLimit - part\n\n\t\tif partsLeft == 1 {\n\t\t\tpr, err := r.findRoute(\n\t\t\t\tremaining, reservations, remainingBudget,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\n\t\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\t\treturn nil, 0, false\n\t\t\t\t}\n\t\t\t\tremainingBudget -= pr.meta.fee\n\t\t\t}\n\n\t\t\tplan = append(plan, pr)\n\t\t\ttotalScore += pr.score\n\t\t\taddReservationSet(reservations, pr.meta)\n\t\t\tremaining = 0\n\t\t\tbreak\n\t\t}\n\n\t\tmaxLimit := remaining -\n\t\t\tlnwire.MilliSatoshi(partsLeft-1)\n\t\tif maxLimit <= 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tmaxRoute, maxAmount, err := r.findLargestRoute(\n\t\t\tmaxLimit, reservations, remainingBudget,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tshard := maxAmount\n\t\tif part == 0 && firstFraction.den > 0 {\n\t\t\tscaled := proportionalAmount(\n\t\t\t\tmaxAmount,\n\t\t\t\tfirstFraction.num,\n\t\t\t\tfirstFraction.den,\n\t\t\t)\n\t\t\tif scaled > 0 && scaled < shard {\n\t\t\t\tshard = scaled\n\t\t\t}\n\t\t}\n\n\t\tvar pr *plannedRoute\n\t\tif shard == maxAmount {\n\t\t\tpr = maxRoute\n\t\t} else {\n\t\t\tpr, err = r.findRoute(\n\t\t\t\tshard, reservations, remainingBudget,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tpr = maxRoute\n\t\t\t\tshard = maxAmount\n\t\t\t}\n\t\t}\n\n\t\tif shard <= 0 || shard >= remaining {\n\t\t\tif shard == remaining {\n\t\t\t\tplan = append(plan, pr)\n\t\t\t\ttotalScore += pr.score\n\t\t\t\tremaining = 0\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremaining -= shard\n\t}\n\n\tif remaining != 0 || len(plan) == 0 {\n\t\treturn nil, 0, false\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= int(slots); parts++ {\n\t\tfor _, allocation := range allocationPatterns(total, parts) {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tgreedyFractions := []amountFraction{\n\t\t{1, 1},\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\n\tfor parts := 2; parts <= int(slots); parts++ {\n\t\tfor _, fraction := range greedyFractions {\n\t\t\tplan, score, ok := r.tryGreedyPlan(\n\t\t\t\ttotal, parts, fraction, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn best, nil\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\n\treturn total\n}\n\nfunc (r *candidateRouter) relaxLocalBounds() bool {\n\tif r.boundRelaxations >= maxBoundRelaxations ||\n\t\tlen(r.localUpper) == 0 {\n\n\t\treturn false\n\t}\n\n\tfor key := range r.localUpper {\n\t\tdelete(r.localUpper, key)\n\t\tdelete(r.localFails, key)\n\t}\n\tr.boundRelaxations++\n\treturn true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"attempt limit reached\")\n\t}\n\tif r.consecutiveMisses >= maxConsecutiveMisses {\n\t\treturn nil, errors.New(\"no progress after repeated failures\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tslots := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 {\n\t\tif queuedAmount(r.plan) != amt ||\n\t\t\tuint32(len(r.plan)) > slots {\n\n\t\t\tr.plan = nil\n\t\t}\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil && r.relaxLocalBounds() {\n\t\t\tplan, err = r.makePlan(amt, slots)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+r.reserved[key])\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tif load <= 0 {\n\t\treturn\n\t}\n\n\tupper := r.localUpper[key]\n\tif upper == 0 || load < upper {\n\t\tr.localUpper[key] = load\n\t}\n\tr.localFails[key]++\n}\n\nfunc (r *candidateRouter) failureEdgeIndex(rt *route.Route,\n\tresult routing.SimHtlcResult) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif result.FailureSource == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.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\t_ = attemptID\n\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.25\n\n\t\t\tif upper := r.localUpper[edge.key];\n\t\t\t\tupper > 0 && meta.loads[i] >= upper {\n\n\t\t\t\tdelete(r.localUpper, edge.key)\n\t\t\t\tdelete(r.localFails, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.committedFees += meta.fee\n\t\tr.consecutiveMisses = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.consecutiveMisses++\n\n\tcode := result.Failure.Code()\n\tfailIdx := r.failureEdgeIndex(rt, result)\n\n\tif failIdx < 0 &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure &&\n\t\tlen(meta.path) > 0 {\n\n\t\tfailIdx = 0\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t\tr.edgePenalty[meta.path[i].key] *= 0.75\n\t}\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\n\t\t\tcount := r.localFails[edge.key]\n\t\t\tr.edgePenalty[edge.key] +=\n\t\t\t\t425_000 + float64(count)*275_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.blocked[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 10_000_000\n\n\t\tdefault:\n\t\t\tr.edgePenalty[edge.key] += 2_500_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 800_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 36,
"parent": 2,
"score": 0.2197,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCLTV = uint32(40)\n\tcandidateRiskPrice = 420000.0\n\tcandidateHopPrice = 7000.0\n\tcandidateReuseNats = 1.85\n\tcandidateMinShard = lnwire.MilliSatoshi(10000)\n\tcandidateProbFloor = 0.012\n\tcandidateProbCeiling = 0.995\n\tcandidateLabelLimit = 16\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 int64\n\tfeeRatePPM int64\n\tcltvDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc (e *candidateEdge) outgoingFee(amt lnwire.MilliSatoshi) int64 {\n\tfee := e.baseFeeMsat + int64(amt)*e.feeRatePPM/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn fee\n}\n\nfunc candidateForwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif incoming == nil || outgoing == nil {\n\t\treturn 0\n\t}\n\n\toutFee := outgoing.outgoingFee(amt)\n\tinBasis := int64(amt) + outFee\n\tinFee := incoming.inboundBase +\n\t\tinBasis*incoming.inboundRate/1_000_000\n\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tsuccess uint32\n\tfailure uint32\n}\n\nvar candidateSharedBeliefs = struct {\n\tsync.Mutex\n\tm map[candidateEdgeKey]candidateBelief\n}{\n\tm: make(map[candidateEdgeKey]candidateBelief),\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn candidateProbFloor\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.045)\n\thighMode := 0.5 / (1 + math.Exp((x-0.94)/0.035))\n\tp := lowMode + highMode\n\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc candidateProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := candidatePrior(load, e.capacity)\n\n\tcandidateSharedBeliefs.Lock()\n\tbelief, ok := candidateSharedBeliefs.m[e.key]\n\tcandidateSharedBeliefs.Unlock()\n\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn candidateProbCeiling\n\t}\n\n\tif belief.upperBad > 0 && load >= belief.upperBad {\n\t\tp := prior * 0.10\n\t\tif p < candidateProbFloor {\n\t\t\tp = candidateProbFloor\n\t\t}\n\t\treturn p\n\t}\n\n\tvar learned, confidence float64\n\n\tswitch {\n\tcase belief.lowerOK > 0 && belief.upperBad > belief.lowerOK:\n\t\tmid := 0.5 * float64(belief.lowerOK+belief.upperBad)\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad-belief.lowerOK)*0.20,\n\t\t\tfloat64(e.capacity)*0.012,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(load)-mid)/width,\n\t\t))\n\t\tconfidence = 0.88\n\n\tcase belief.upperBad > 0:\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad)*0.16,\n\t\t\tfloat64(e.capacity)*0.018,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(load)-float64(belief.upperBad))/width,\n\t\t))\n\t\tconfidence = 0.72\n\n\tcase belief.lowerOK > 0:\n\t\texcess := float64(load-belief.lowerOK) /\n\t\t\tmath.Max(float64(e.capacity), 1)\n\t\tlearned = candidateProbCeiling * math.Exp(-excess/0.14)\n\t\tconfidence = 0.64\n\n\tdefault:\n\t\treturn prior\n\t}\n\n\tp := confidence*learned + (1-confidence)*prior\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > candidateProbCeiling {\n\t\treturn candidateProbCeiling\n\t}\n\treturn p\n}\n\nfunc candidateRecordSuccess(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tb.success++\n\n\tif b.upperBad > 0 && b.lowerOK >= b.upperBad {\n\t\tb.upperBad = b.lowerOK + 1\n\t}\n\n\tcandidateSharedBeliefs.m[key] = b\n}\n\nfunc candidateRecordFailure(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif b.upperBad == 0 || load < b.upperBad {\n\t\tb.upperBad = load\n\t}\n\tb.failure++\n\n\tcandidateSharedBeliefs.m[key] = b\n}\n\ntype candidateFailurePenalty struct {\n\tamount lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateSearchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk float64\n\thops int\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype candidateSearchQueue []*candidateSearchItem\n\nfunc (q candidateSearchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateSearchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateSearchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateSearchQueue) Push(x any) {\n\titem := x.(*candidateSearchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateSearchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocal map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route]bool\n\tfailures map[candidateEdgeKey]candidateFailurePenalty\n\tbanned map[candidateEdgeKey]bool\n\n\tplan []*route.Route\n\tcommittedFees lnwire.MilliSatoshi\n\tnumFailures 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[candidateEdgeKey]*candidateEdge),\n\t\tlocal: localBalances,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route]bool),\n\t\tfailures: make(map[candidateEdgeKey]candidateFailurePenalty),\n\t\tbanned: make(map[candidateEdgeKey]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\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: int64(policy.FeeBaseMSat),\n\t\t\t\t\tfeeRatePPM: int64(\n\t\t\t\t\t\tpolicy.FeeProportionalMillionths,\n\t\t\t\t\t),\n\t\t\t\t\tcltvDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t\tinboundBase: int64(\n\t\t\t\t\t\tch.InboundFee.BaseFee,\n\t\t\t\t\t),\n\t\t\t\t\tinboundRate: int64(\n\t\t\t\t\t\tch.InboundFee.FeeRate,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(\n\t\t\t\t\tr.incoming[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateAdd(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b > 0 && a > lnwire.MaxMilliSatoshi-b {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn a + b\n}\n\nfunc (r *candidateRouter) budgetLeft() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) routeFee(\n\trt *route.Route) lnwire.MilliSatoshi {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeAmount(\n\trt *route.Route) lnwire.MilliSatoshi {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidatePathContains(path []*candidateEdge,\n\tnode route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAcceptLabel(labels map[route.Vertex][]float64,\n\tnode route.Vertex, score float64) bool {\n\n\tcurrent := labels[node]\n\tif len(current) < candidateLabelLimit {\n\t\tlabels[node] = append(current, score)\n\t\treturn true\n\t}\n\n\tworstIndex := 0\n\tfor i := 1; i < len(current); i++ {\n\t\tif current[i] > current[worstIndex] {\n\t\t\tworstIndex = i\n\t\t}\n\t}\n\n\tif score >= current[worstIndex] {\n\t\treturn false\n\t}\n\n\tcurrent[worstIndex] = score\n\tlabels[node] = current\n\treturn true\n}\n\nfunc candidateLabelLive(labels map[route.Vertex][]float64,\n\tnode route.Vertex, score float64) bool {\n\n\tcurrent := labels[node]\n\tif len(current) < candidateLabelLimit {\n\t\treturn true\n\t}\n\n\tworst := current[0]\n\tfor _, value := range current[1:] {\n\t\tif value > worst {\n\t\t\tworst = value\n\t\t}\n\t}\n\n\treturn score <= worst+0.0001\n}\n\nfunc (r *candidateRouter) failurePenalty(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tfailure, ok := r.failures[key]\n\tif !ok || failure.amount <= 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(load) / float64(failure.amount)\n\tbase := 0.72 + 0.30*float64(failure.count)\n\tif base > 3.2 {\n\t\tbase = 3.2\n\t}\n\n\tswitch {\n\tcase ratio <= 0.50:\n\t\treturn base * 0.025\n\tcase ratio < 0.82:\n\t\tx := (ratio - 0.50) / 0.32\n\t\treturn base * (0.025 + 0.30*x*x)\n\tcase ratio < 1:\n\t\tx := (ratio - 0.82) / 0.18\n\t\treturn base * (0.325 + 0.675*x)\n\tdefault:\n\t\treturn base\n\t}\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid shard amount\")\n\t}\n\n\tlabels := make(map[route.Vertex][]float64)\n\tlabels[r.spec.Target] = []float64{0}\n\n\tpq := &candidateSearchQueue{}\n\theap.Push(pq, &candidateSearchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateSearchItem)\n\t\tif !candidateLabelLive(labels, item.node, item.score) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item.path, deliver)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\tif r.routeFee(rt) > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tquality := 1.0\n\t\t\tif item.hops > 0 {\n\t\t\t\tquality = math.Exp(\n\t\t\t\t\t-item.risk / float64(item.hops),\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn rt, quality, nil\n\t\t}\n\n\t\tvar outgoing *candidateEdge\n\t\tif len(item.path) > 0 {\n\t\t\toutgoing = item.path[0]\n\t\t}\n\n\t\tfor _, incoming := range r.incoming[item.node] {\n\t\t\tif r.banned[incoming.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif candidatePathContains(\n\t\t\t\titem.path, incoming.key.from,\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOver := item.amount\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tamountOver = candidateAdd(\n\t\t\t\t\tamountOver,\n\t\t\t\t\tcandidateForwardingFee(\n\t\t\t\t\t\tincoming, outgoing, item.amount,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif !incoming.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi &&\n\t\t\t\tamountOver-deliver > maxFee {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := candidateAdd(\n\t\t\t\tamountOver, r.reserved[incoming.key],\n\t\t\t)\n\t\t\tif extra != nil {\n\t\t\t\tload = candidateAdd(load, extra[incoming.key])\n\t\t\t}\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar probability float64\n\t\t\tif incoming.key.from == r.source {\n\t\t\t\tbalance, ok := r.local[incoming.key.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprobability = 0.999\n\t\t\t} else {\n\t\t\t\tprobability = candidateProbability(\n\t\t\t\t\tincoming, load,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability)\n\t\t\tpenalty := r.failurePenalty(incoming.key, load)\n\t\t\treuse := 0.0\n\t\t\tif r.reserved[incoming.key] > 0 ||\n\t\t\t\t(extra != nil && extra[incoming.key] > 0) {\n\n\t\t\t\treuse = candidateReuseNats\n\t\t\t}\n\n\t\t\tfeeIncrement := float64(amountOver - item.amount)\n\t\t\tnewScore := item.score + feeIncrement +\n\t\t\t\tcandidateRiskPrice*(risk+penalty+reuse) +\n\t\t\t\tcandidateHopPrice\n\n\t\t\tif !candidateAcceptLabel(\n\t\t\t\tlabels, incoming.key.from, newScore,\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = incoming\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\theap.Push(pq, &candidateSearchItem{\n\t\t\t\tnode: incoming.key.from,\n\t\t\t\tamount: amountOver,\n\t\t\t\tscore: newScore,\n\t\t\t\trisk: item.risk + risk,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tdeliver lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = candidateFinalCLTV\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := candidateForwardingFee(\n\t\t\tpath[i], path[i+1], amounts[i+1],\n\t\t)\n\t\tamounts[i] = candidateAdd(amounts[i+1], fee)\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].cltvDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := candidateFinalCLTV\n\t\tif i < n-1 {\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) forEachRouteEdge(rt *route.Route,\n\tfn func(candidateEdgeKey, lnwire.MilliSatoshi, int)) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tfn(key, amount, i)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc (r *candidateRouter) addVirtualReservation(\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\treserved[key] = candidateAdd(reserved[key], amount)\n\t})\n}\n\nfunc (r *candidateRouter) reserveIssued(rt *route.Route) {\n\tif r.issued[rt] {\n\t\treturn\n\t}\n\n\tr.issued[rt] = true\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tr.reserved[key] = candidateAdd(\n\t\t\tr.reserved[key], amount,\n\t\t)\n\t})\n}\n\nfunc (r *candidateRouter) releaseIssued(rt *route.Route) {\n\tif !r.issued[rt] {\n\t\treturn\n\t}\n\n\tdelete(r.issued, rt)\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tcurrent := r.reserved[key]\n\t\tif current <= amount {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = current - amount\n\t\t}\n\t})\n}\n\nfunc candidateAppendAmount(values []lnwire.MilliSatoshi,\n\tvalue, minimum, maximum 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 {\n\t\treturn values\n\t}\n\n\tfor _, existing := range values {\n\t\tif existing == value {\n\t\t\treturn values\n\t\t}\n\t}\n\n\treturn append(values, value)\n}\n\nfunc (r *candidateRouter) tryPlan(amount lnwire.MilliSatoshi,\n\tparts, biasNum, biasDen int) ([]*route.Route, bool, float64) {\n\n\tif parts < 1 || biasNum < 1 || biasDen < 1 {\n\t\treturn nil, false, 0\n\t}\n\n\tremaining := amount\n\tvirtual := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tplannedFees := lnwire.MilliSatoshi(0)\n\tplan := make([]*route.Route, 0, parts)\n\tplanScore := 0.0\n\n\tfor i := 0; i < parts; i++ {\n\t\tleft := parts - i\n\t\tminimumLater := candidateMinShard *\n\t\t\tlnwire.MilliSatoshi(left-1)\n\n\t\tif remaining < candidateMinShard*\n\t\t\tlnwire.MilliSatoshi(left) {\n\n\t\t\treturn nil, false, 0\n\t\t}\n\n\t\tif left == 1 {\n\t\t\tmaxFee := r.budgetLeft()\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi {\n\t\t\t\tif plannedFees >= maxFee {\n\t\t\t\t\tmaxFee = 0\n\t\t\t\t} else {\n\t\t\t\t\tmaxFee -= plannedFees\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trt, quality, err := r.findRoute(\n\t\t\t\tremaining, virtual, maxFee,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false, 0\n\t\t\t}\n\n\t\t\tfee := r.routeFee(rt)\n\t\t\tplan = append(plan, rt)\n\t\t\tplannedFees = candidateAdd(plannedFees, fee)\n\t\t\tplanScore += float64(fee) +\n\t\t\t\tcandidateRiskPrice*\n\t\t\t\t\t(-math.Log(quality))*\n\t\t\t\t\tfloat64(len(rt.Hops))\n\t\t\tremaining = 0\n\t\t\tcontinue\n\t\t}\n\n\t\tbase := remaining / lnwire.MilliSatoshi(left)\n\t\tmaximum := remaining - minimumLater\n\t\ttarget := base * lnwire.MilliSatoshi(biasNum) /\n\t\t\tlnwire.MilliSatoshi(biasDen)\n\n\t\tvar amounts []lnwire.MilliSatoshi\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, target, candidateMinShard, maximum,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base+base/4,\n\t\t\tcandidateMinShard, maximum,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base, candidateMinShard, maximum,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base*3/4,\n\t\t\tcandidateMinShard, maximum,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base/2,\n\t\t\tcandidateMinShard, maximum,\n\t\t)\n\n\t\tvar selected *route.Route\n\t\tselectedQuality := 0.0\n\n\t\tfor _, shard := range amounts {\n\t\t\tmaxFee := r.budgetLeft()\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi {\n\t\t\t\tif plannedFees >= maxFee {\n\t\t\t\t\tmaxFee = 0\n\t\t\t\t} else {\n\t\t\t\t\tmaxFee -= plannedFees\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trt, quality, err := r.findRoute(\n\t\t\t\tshard, virtual, maxFee,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tselected = rt\n\t\t\tselectedQuality = quality\n\t\t\tbreak\n\t\t}\n\n\t\tif selected == nil {\n\t\t\treturn nil, false, 0\n\t\t}\n\n\t\tfee := r.routeFee(selected)\n\t\tif r.budgetLeft() != lnwire.MaxMilliSatoshi &&\n\t\t\tplannedFees+fee > r.budgetLeft() {\n\n\t\t\treturn nil, false, 0\n\t\t}\n\n\t\tplan = append(plan, selected)\n\t\tplannedFees = candidateAdd(plannedFees, fee)\n\t\tplanScore += float64(fee) +\n\t\t\tcandidateRiskPrice*\n\t\t\t\t(-math.Log(selectedQuality))*\n\t\t\t\tfloat64(len(selected.Hops))\n\n\t\tr.addVirtualReservation(virtual, selected)\n\t\tremaining -= r.routeAmount(selected)\n\t}\n\n\treturn plan, remaining == 0, planScore\n}\n\nfunc (r *candidateRouter) bestPlan(amount lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, bool) {\n\n\tbiases := [][2]int{\n\t\t{3, 2},\n\t\t{5, 4},\n\t\t{1, 1},\n\t\t{3, 4},\n\t}\n\n\tvar best []*route.Route\n\tbestScore := math.Inf(1)\n\n\tfor _, bias := range biases {\n\t\tplan, ok, score := r.tryPlan(\n\t\t\tamount, parts, bias[0], bias[1],\n\t\t)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif score < bestScore {\n\t\t\tbest = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\treturn best, best != nil\n}\n\nfunc (r *candidateRouter) makePlan(amount lnwire.MilliSatoshi,\n\tslots uint32) ([]*route.Route, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tfull, quality, fullErr := r.findRoute(\n\t\tamount, nil, r.budgetLeft(),\n\t)\n\n\tif slots == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tpreferred := 1\n\tswitch {\n\tcase fullErr != nil:\n\t\tpreferred = 2\n\tcase quality < 0.48:\n\t\tpreferred = 3\n\tcase quality < 0.68:\n\t\tpreferred = 2\n\tcase amount >= 300_000_000 && quality < 0.78:\n\t\tpreferred = 2\n\t}\n\n\tif r.numFailures > 0 && preferred < 2 {\n\t\tpreferred = 2\n\t}\n\tif r.numFailures >= 3 && preferred < 3 {\n\t\tpreferred = 3\n\t}\n\tif preferred > int(slots) {\n\t\tpreferred = int(slots)\n\t}\n\n\tif preferred == 1 && fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tfor parts := preferred; parts <= int(slots); parts++ {\n\t\tplan, ok := r.bestPlan(amount, parts)\n\t\tif ok {\n\t\t\treturn plan, nil\n\t\t}\n\t}\n\n\tif fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tshard := amount / lnwire.MilliSatoshi(slots)\n\tif shard < candidateMinShard {\n\t\tshard = candidateMinShard\n\t}\n\n\tfor shard >= candidateMinShard {\n\t\trt, _, err := r.findRoute(\n\t\t\tshard, nil, r.budgetLeft(),\n\t\t)\n\t\tif err == nil {\n\t\t\treturn []*route.Route{rt}, nil\n\t\t}\n\n\t\tif shard == candidateMinShard {\n\t\t\tbreak\n\t\t}\n\n\t\tshard = shard * 2 / 3\n\t\tif shard < candidateMinShard {\n\t\t\tshard = candidateMinShard\n\t\t}\n\t}\n\n\treturn nil, fullErr\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tfor {\n\t\tif len(r.plan) == 0 {\n\t\t\tmaxParts := r.spec.MaxParts\n\t\t\tif maxParts == 0 {\n\t\t\t\tmaxParts = 1\n\t\t\t}\n\t\t\tif inFlightHtlcs >= maxParts {\n\t\t\t\treturn nil, errors.New(\n\t\t\t\t\t\"maximum parts already in flight\",\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tplan, err := r.makePlan(\n\t\t\t\tamt, maxParts-inFlightHtlcs,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr.plan = plan\n\t\t}\n\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tif r.routeAmount(rt) > amt {\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tbudget := r.budgetLeft()\n\t\tif budget != lnwire.MaxMilliSatoshi && fee > budget {\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserveIssued(rt)\n\t\treturn rt, nil\n\t}\n}\n\nfunc (r *candidateRouter) routeFailureIndex(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) routeEdgeAt(rt *route.Route,\n\tindex int) (candidateEdgeKey, lnwire.MilliSatoshi, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tamount := rt.TotalAmount\n\tif index > 0 {\n\t\tamount = rt.Hops[index-1].AmtToForward\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, amount, true\n}\n\nfunc (r *candidateRouter) observedLoad(key candidateEdgeKey,\n\tfallback lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif load := r.reserved[key]; load > 0 {\n\t\treturn load\n\t}\n\treturn fallback\n}\n\nfunc (r *candidateRouter) noteFailure(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\told := r.failures[key]\n\tif old.amount == 0 || load < old.amount {\n\t\told.amount = load\n\t}\n\told.count++\n\tr.failures[key] = old\n}\n\nfunc (r *candidateRouter) noteSuccess(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\told, ok := r.failures[key]\n\tif !ok {\n\t\treturn\n\t}\n\n\tif load >= old.amount || old.count <= 1 {\n\t\tdelete(r.failures, key)\n\t\treturn\n\t}\n\n\told.count--\n\tr.failures[key] = old\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\t\tload := r.observedLoad(key, amount)\n\t\t\tcandidateRecordSuccess(key, load)\n\t\t\tr.noteSuccess(key, load)\n\t\t})\n\n\t\tr.committedFees = candidateAdd(\n\t\t\tr.committedFees, r.routeFee(rt),\n\t\t)\n\t\treturn nil\n\t}\n\n\tr.numFailures++\n\tr.plan = nil\n\n\tfailIndex := r.routeFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tfor i := 0; i < failIndex && i < len(rt.Hops); i++ {\n\t\tkey, amount, ok := r.routeEdgeAt(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tload := r.observedLoad(key, amount)\n\t\tcandidateRecordSuccess(key, load)\n\t\tr.noteSuccess(key, load)\n\t}\n\n\tcode := result.Failure.Code()\n\tif key, amount, ok := r.routeEdgeAt(\n\t\trt, failIndex,\n\t); ok {\n\t\tload := r.observedLoad(key, amount)\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tcandidateRecordFailure(key, load)\n\t\t\tr.noteFailure(key, load)\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.banned[key] = true\n\n\t\tdefault:\n\t\t\tr.noteFailure(key, load)\n\t\t}\n\t} else {\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\t\tload := r.observedLoad(key, amount)\n\t\t\told := r.failures[key]\n\t\t\tif old.amount == 0 || load < old.amount {\n\t\t\t\told.amount = load\n\t\t\t}\n\t\t\tif old.count < 2 {\n\t\t\t\told.count++\n\t\t\t}\n\t\t\tr.failures[key] = old\n\t\t})\n\t}\n\n\tr.releaseIssued(rt)\n\treturn nil\n}"
}
},
{
"id": 37,
"parent": 1,
"score": 0.1076,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\tbaseRiskPriceMsat = float64(420_000)\n\tminRiskPriceMsat = float64(25_000)\n\thopCostMsat = float64(8_000)\n\treusedEdgeCostMsat = float64(650_000)\n\textraPartCostMsat = float64(50_000)\n\tmaxSearchHops = 60\n\tmaxLabelsPerNode = 10\n\tmaxRouterAttempts = uint32(80)\n\tminFallbackShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf float64\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int32\n\tinboundRate int32\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\nfunc (e *candidateEdge) publicCeiling() lnwire.MilliSatoshi {\n\tlimit := e.capacity\n\tif e.maxHTLC != 0 && e.maxHTLC < limit {\n\t\tlimit = e.maxHTLC\n\t}\n\treturn limit\n}\n\nfunc nodeFee(in, out *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\toutbound := int64(out.fee(amt))\n\tinbound := int64(in.inboundBase) +\n\t\tint64(amt)*int64(in.inboundRate)/1_000_000\n\n\ttotal := outbound + inbound\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype routeMeta struct {\n\tpath []*candidateEdge\n\tamounts []lnwire.MilliSatoshi\n\tloads []lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\trt *route.Route\n\tmeta *routeMeta\n\tscore float64\n}\n\ntype localFailure struct {\n\tupperBad lnwire.MilliSatoshi\n\tretryAt uint32\n\tfailCount uint32\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeLookup map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[edgeKey]float64\n\tlocalFailures map[edgeKey]localFailure\n\tpolicyBad map[edgeKey]bool\n\tissued map[*route.Route]*routeMeta\n\tplan []*plannedRoute\n\n\tcommittedFees lnwire.MilliSatoshi\n\tbudgetGuard lnwire.MilliSatoshi\n\tsuccessfulParts uint32\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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeLookup: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\tlocalFailures: make(map[edgeKey]localFailure),\n\t\tpolicyBad: make(map[edgeKey]bool),\n\t\tissued: make(map[*route.Route]*routeMeta),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t\tinboundBase: ch.InboundFee.BaseFee,\n\t\t\t\t\tinboundRate: ch.InboundFee.FeeRate,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeLookup[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.025)\n\thighMode := 0.49 / (1 + math.Exp((x-0.97)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc beliefConfidence(b liquidityBelief, now time.Time) float64 {\n\tif b.updated.IsZero() || now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\tif age <= 0 {\n\t\treturn b.conf\n\t}\n\n\treturn b.conf * math.Exp(-age.Seconds()/1800)\n}\n\nfunc channelProbability(key edgeKey, amt, capacity lnwire.MilliSatoshi,\n\tnow time.Time) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tsharedBeliefs.Lock()\n\tb, ok := sharedBeliefs.m[key]\n\tsharedBeliefs.Unlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tconf := beliefConfidence(b, now)\n\tif conf < 0.01 {\n\t\treturn prior\n\t}\n\n\tevidence := prior\n\thasEvidence := false\n\n\tswitch {\n\tcase b.lowerOK > 0 && amt <= b.lowerOK:\n\t\tevidence = 0.995\n\t\thasEvidence = true\n\n\tcase b.upperBad > 0 && amt >= b.upperBad:\n\t\tevidence = 0.012\n\t\thasEvidence = true\n\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(b.upperBad-amt) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence = 0.012 + 0.983*pos\n\t\thasEvidence = true\n\t}\n\n\tif !hasEvidence {\n\t\treturn prior\n\t}\n\n\tp := prior*(1-conf) + evidence*conf\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc updateBelief(key edgeKey, amt lnwire.MilliSatoshi, success bool,\n\tnow time.Time) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tdefer sharedBeliefs.Unlock()\n\n\tb := sharedBeliefs.m[key]\n\tif !b.updated.IsZero() && now.Before(b.updated) {\n\t\tb = liquidityBelief{}\n\t} else {\n\t\tb.conf = beliefConfidence(b, now)\n\t}\n\n\tif success {\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t\tif b.upperBad != 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\t} else {\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t\tif b.lowerOK >= amt {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tb.conf = math.Min(1, b.conf+0.55)\n\tb.updated = now\n\tsharedBeliefs.m[key] = b\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\tscore float64\n\thops int\n\tnextEdge *candidateEdge\n\tchild *searchState\n\tactive bool\n}\n\ntype searchQueue []*searchState\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *searchQueue) Push(x any) {\n\t*q = append(*q, x.(*searchState))\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc containsNode(s *searchState, node route.Vertex) bool {\n\tfor cur := s; cur != nil; cur = cur.child {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addSearchLabel(labels map[route.Vertex][]*searchState,\n\ts *searchState) bool {\n\n\told := labels[s.node]\n\tkept := old[:0]\n\n\tfor _, other := range old {\n\t\tif !other.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tif other.score <= s.score &&\n\t\t\tother.fee <= s.fee &&\n\t\t\tother.amount <= s.amount {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif s.score <= other.score &&\n\t\t\ts.fee <= other.fee &&\n\t\t\ts.amount <= other.amount {\n\n\t\t\tother.active = false\n\t\t\tcontinue\n\t\t}\n\n\t\tkept = append(kept, other)\n\t}\n\n\ts.active = true\n\tkept = append(kept, s)\n\n\tif len(kept) > maxLabelsPerNode {\n\t\tprotected := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].fee < kept[protected].fee {\n\t\t\t\tprotected = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range kept {\n\t\t\tif i == protected {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 || kept[i].score > kept[worst].score {\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\n\t\tif worst >= 0 {\n\t\t\tremoved := kept[worst]\n\t\t\tremoved.active = false\n\t\t\tkept = append(kept[:worst], kept[worst+1:]...)\n\t\t\tif removed == s {\n\t\t\t\tlabels[s.node] = kept\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tlabels[s.node] = kept\n\treturn true\n}\n\nfunc (r *candidateRouter) remainingFeeBudget() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\n\tspent := r.committedFees + r.budgetGuard\n\tif spent < r.committedFees || spent >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - spent\n}\n\nfunc riskPrice(feeCap lnwire.MilliSatoshi) float64 {\n\tif feeCap == lnwire.MaxMilliSatoshi {\n\t\treturn baseRiskPriceMsat\n\t}\n\n\tprice := float64(feeCap) / 2\n\tif price < minRiskPriceMsat {\n\t\tprice = minRiskPriceMsat\n\t}\n\tif price > baseRiskPriceMsat {\n\t\tprice = baseRiskPriceMsat\n\t}\n\treturn price\n}\n\nfunc cloneReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) edgeCooling(key edgeKey,\n\tload lnwire.MilliSatoshi) bool {\n\n\tfailure, ok := r.localFailures[key]\n\tif !ok || failure.upperBad == 0 || load < failure.upperBad {\n\t\treturn false\n\t}\n\treturn r.attempts < failure.retryAt\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tfeeCap lnwire.MilliSatoshi) (*plannedRoute, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tnow := r.view.Now()\n\tprice := riskPrice(feeCap)\n\tlabels := make(map[route.Vertex][]*searchState)\n\tpq := &searchQueue{}\n\n\tstart := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t}\n\tlabels[start.node] = []*searchState{start}\n\theap.Push(pq, start)\n\n\tvar result *searchState\n\n\tfor pq.Len() > 0 {\n\t\tcur := heap.Pop(pq).(*searchState)\n\t\tif !cur.active {\n\t\t\tcontinue\n\t\t}\n\t\tif cur.node == r.source {\n\t\t\tresult = cur\n\t\t\tbreak\n\t\t}\n\t\tif cur.hops >= maxSearchHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[cur.node] {\n\t\t\tif r.policyBad[edge.key] ||\n\t\t\t\tcontainsNode(cur, edge.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tover := cur.amount\n\t\t\tnodeCharge := lnwire.MilliSatoshi(0)\n\t\t\tif cur.node != r.spec.Target {\n\t\t\t\tif cur.nextEdge == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnodeCharge = nodeFee(edge, cur.nextEdge, cur.amount)\n\t\t\t\tif nodeCharge > lnwire.MaxMilliSatoshi-cur.amount {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tover += nodeCharge\n\t\t\t}\n\n\t\t\tif !edge.usableHTLC(over) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := reservations[edge.key]\n\t\t\tif reserved > lnwire.MaxMilliSatoshi-over {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttotalLoad := over + reserved\n\t\t\tif totalLoad > edge.capacity ||\n\t\t\t\tr.edgeCooling(edge.key, totalLoad) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\t\t\tif !ok || totalLoad > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttotalFee := cur.fee + nodeCharge\n\t\t\tif totalFee < cur.fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif feeCap != lnwire.MaxMilliSatoshi &&\n\t\t\t\ttotalFee > feeCap {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := channelProbability(\n\t\t\t\tedge.key, totalLoad, edge.capacity, now,\n\t\t\t)\n\t\t\tif edge.from == r.source {\n\t\t\t\tp = 0.99\n\t\t\t}\n\n\t\t\tscore := cur.score + float64(nodeCharge) +\n\t\t\t\tprice*(-math.Log(p)) + hopCostMsat +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tif reserved > 0 {\n\t\t\t\treuse := reusedEdgeCostMsat\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\treuse *= 0.45\n\t\t\t\t}\n\t\t\t\tscore += reuse\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: over,\n\t\t\t\tfee: totalFee,\n\t\t\t\tscore: score,\n\t\t\t\thops: cur.hops + 1,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tchild: cur,\n\t\t\t}\n\t\t\tif addSearchLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif result == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, result.hops)\n\tfor cur := result; cur.node != r.spec.Target; cur = cur.child {\n\t\tif cur.nextEdge == nil || cur.child == nil {\n\t\t\treturn nil, errors.New(\"broken route state\")\n\t\t}\n\t\tpath = append(path, cur.nextEdge)\n\t}\n\n\trt, amounts, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloads := make([]lnwire.MilliSatoshi, len(path))\n\tfor i, edge := range path {\n\t\treserved := reservations[edge.key]\n\t\tif reserved > lnwire.MaxMilliSatoshi-amounts[i] {\n\t\t\treturn nil, errors.New(\"route load overflow\")\n\t\t}\n\t\tloads[i] = amounts[i] + reserved\n\t}\n\n\tmeta := &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: amt,\n\t\tfee: rt.TotalAmount - amt,\n\t}\n\tif feeCap != lnwire.MaxMilliSatoshi && meta.fee > feeCap {\n\t\treturn nil, errors.New(\"route exceeds fee budget\")\n\t}\n\n\treturn &plannedRoute{\n\t\trt: rt,\n\t\tmeta: meta,\n\t\tscore: result.score,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, []lnwire.MilliSatoshi, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, errors.New(\"empty path\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\tamounts[n-1] = amt\n\texpiries[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tcharge := nodeFee(path[i], path[i+1], amounts[i+1])\n\t\tif charge > lnwire.MaxMilliSatoshi-amounts[i+1] {\n\t\t\treturn nil, nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\tamounts[i] = amounts[i+1] + charge\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, amounts, nil\n}\n\nfunc weightedAllocation(total lnwire.MilliSatoshi,\n\tweights []int64) []lnwire.MilliSatoshi {\n\n\tresult := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\tvar remainingWeight int64\n\tfor _, weight := range weights {\n\t\tif weight > 0 {\n\t\t\tremainingWeight += weight\n\t\t}\n\t}\n\n\tfor i, weight := range weights {\n\t\tleft := len(weights) - i\n\t\tif left == 1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tmaxPart := remaining - lnwire.MilliSatoshi(left-1)\n\t\tpart := lnwire.MilliSatoshi(\n\t\t\tfloat64(remaining) * float64(weight) /\n\t\t\t\tfloat64(remainingWeight),\n\t\t)\n\t\tif part < 1 {\n\t\t\tpart = 1\n\t\t}\n\t\tif part > maxPart {\n\t\t\tpart = maxPart\n\t\t}\n\n\t\tresult[i] = part\n\t\tremaining -= part\n\t\tremainingWeight -= weight\n\t}\n\n\treturn result\n}\n\nfunc sameAllocation(a, b []lnwire.MilliSatoshi) 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 appendAllocation(patterns *[][]lnwire.MilliSatoshi,\n\tallocation []lnwire.MilliSatoshi) {\n\n\tfor _, old := range *patterns {\n\t\tif sameAllocation(old, allocation) {\n\t\t\treturn\n\t\t}\n\t}\n\t*patterns = append(*patterns, allocation)\n}\n\nfunc reverseWeights(weights []int64) []int64 {\n\treversed := make([]int64, len(weights))\n\tfor i := range weights {\n\t\treversed[len(weights)-1-i] = weights[i]\n\t}\n\treturn reversed\n}\n\nfunc (r *candidateRouter) capacityWeights(parts int) []int64 {\n\tsourceCaps := make([]lnwire.MilliSatoshi, 0)\n\ttargetCaps := make([]lnwire.MilliSatoshi, 0)\n\n\tfor _, edge := range r.edgeLookup {\n\t\tif edge.from != r.source {\n\t\t\tcontinue\n\t\t}\n\n\t\tlimit := edge.publicCeiling()\n\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif balance < limit {\n\t\t\tlimit = balance\n\t\t}\n\t\tif limit > 0 {\n\t\t\tsourceCaps = append(sourceCaps, limit)\n\t\t}\n\t}\n\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tlimit := edge.publicCeiling()\n\t\tif limit > 0 {\n\t\t\ttargetCaps = append(targetCaps, limit)\n\t\t}\n\t}\n\n\tif len(sourceCaps) == 0 || len(targetCaps) == 0 {\n\t\treturn nil\n\t}\n\n\tsort.Slice(sourceCaps, func(i, j int) bool {\n\t\treturn sourceCaps[i] > sourceCaps[j]\n\t})\n\tsort.Slice(targetCaps, func(i, j int) bool {\n\t\treturn targetCaps[i] > targetCaps[j]\n\t})\n\n\traw := make([]lnwire.MilliSatoshi, parts)\n\tvar largest lnwire.MilliSatoshi\n\tfor i := 0; i < parts; i++ {\n\t\tsi := i\n\t\tif si >= len(sourceCaps) {\n\t\t\tsi = len(sourceCaps) - 1\n\t\t}\n\t\tti := i\n\t\tif ti >= len(targetCaps) {\n\t\t\tti = len(targetCaps) - 1\n\t\t}\n\n\t\traw[i] = sourceCaps[si]\n\t\tif targetCaps[ti] < raw[i] {\n\t\t\traw[i] = targetCaps[ti]\n\t\t}\n\t\tif raw[i] > largest {\n\t\t\tlargest = raw[i]\n\t\t}\n\t}\n\tif largest <= 0 {\n\t\treturn nil\n\t}\n\n\tweights := make([]int64, parts)\n\tfor i, capacity := range raw {\n\t\tweight := int64(\n\t\t\t1 + 999*float64(capacity)/float64(largest),\n\t\t)\n\t\tif weight < 1 {\n\t\t\tweight = 1\n\t\t}\n\t\tweights[i] = weight\n\t}\n\treturn weights\n}\n\nfunc (r *candidateRouter) allocationPatterns(\n\ttotal lnwire.MilliSatoshi, parts int) [][]lnwire.MilliSatoshi {\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil\n\t}\n\n\tpatterns := make([][]lnwire.MilliSatoshi, 0, 8)\n\tequal := make([]int64, parts)\n\tfor i := range equal {\n\t\tequal[i] = 1\n\t}\n\tappendAllocation(&patterns, weightedAllocation(total, equal))\n\n\tswitch parts {\n\tcase 1:\n\t\treturn patterns\n\n\tcase 2:\n\t\tfor _, weights := range [][]int64{\n\t\t\t{45, 55}, {55, 45},\n\t\t\t{35, 65}, {65, 35},\n\t\t\t{25, 75}, {75, 25},\n\t\t} {\n\t\t\tappendAllocation(\n\t\t\t\t&patterns, weightedAllocation(total, weights),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\tascending := make([]int64, parts)\n\t\theavy := make([]int64, parts)\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tascending[i] = int64(i + 1)\n\t\t\theavy[i] = 1\n\t\t}\n\t\theavy[0] = int64(parts)\n\n\t\tappendAllocation(\n\t\t\t&patterns, weightedAllocation(total, ascending),\n\t\t)\n\t\tappendAllocation(\n\t\t\t&patterns,\n\t\t\tweightedAllocation(total, reverseWeights(ascending)),\n\t\t)\n\t\tappendAllocation(\n\t\t\t&patterns, weightedAllocation(total, heavy),\n\t\t)\n\t\tappendAllocation(\n\t\t\t&patterns,\n\t\t\tweightedAllocation(total, reverseWeights(heavy)),\n\t\t)\n\t}\n\n\tif weights := r.capacityWeights(parts); len(weights) != 0 {\n\t\tappendAllocation(\n\t\t\t&patterns, weightedAllocation(total, weights),\n\t\t)\n\t\tappendAllocation(\n\t\t\t&patterns,\n\t\t\tweightedAllocation(total, reverseWeights(weights)),\n\t\t)\n\t}\n\n\treturn patterns\n}\n\nfunc addReservationSet(dst map[edgeKey]lnwire.MilliSatoshi,\n\tmeta *routeMeta) {\n\n\tfor i, edge := range meta.path {\n\t\tdst[edge.key] += meta.amounts[i]\n\t}\n}\n\nfunc proportionalBudget(budget, part,\n\ttotal lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif budget == lnwire.MaxMilliSatoshi {\n\t\treturn budget\n\t}\n\tif total <= 0 || part >= total {\n\t\treturn budget\n\t}\n\n\tshare := lnwire.MilliSatoshi(\n\t\tfloat64(budget) * float64(part) / float64(total),\n\t)\n\tif share < 0 {\n\t\treturn 0\n\t}\n\tif share > budget {\n\t\treturn budget\n\t}\n\treturn share\n}\n\nfunc (r *candidateRouter) tryAllocation(\n\tallocation []lnwire.MilliSatoshi,\n\tfeeBudget lnwire.MilliSatoshi) ([]*plannedRoute, float64, bool) {\n\n\treservations := cloneReservations(r.reserved)\n\tremainingBudget := feeBudget\n\tvar remainingAmount lnwire.MilliSatoshi\n\tfor _, shard := range allocation {\n\t\tif shard <= 0 ||\n\t\t\tshard > lnwire.MaxMilliSatoshi-remainingAmount {\n\n\t\t\treturn nil, 0, false\n\t\t}\n\t\tremainingAmount += shard\n\t}\n\n\tplan := make([]*plannedRoute, 0, len(allocation))\n\ttotalScore := float64(0)\n\n\tfor _, shard := range allocation {\n\t\tcap := remainingBudget\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi &&\n\t\t\tshard < remainingAmount {\n\n\t\t\tcap = proportionalBudget(\n\t\t\t\tremainingBudget, shard, remainingAmount,\n\t\t\t)\n\t\t\tslack := remainingBudget / 20\n\t\t\tif cap <= remainingBudget-slack {\n\t\t\t\tcap += slack\n\t\t\t} else {\n\t\t\t\tcap = remainingBudget\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, reservations, cap)\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tif remainingBudget != lnwire.MaxMilliSatoshi {\n\t\t\tif pr.meta.fee > remainingBudget {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\t\t\tremainingBudget -= pr.meta.fee\n\t\t}\n\n\t\tplan = append(plan, pr)\n\t\ttotalScore += pr.score\n\t\taddReservationSet(reservations, pr.meta)\n\t\tremainingAmount -= shard\n\t}\n\n\ttotalScore += float64(len(plan)-1) * extraPartCostMsat\n\treturn plan, totalScore, true\n}\n\nfunc (r *candidateRouter) maximumPublicShard() lnwire.MilliSatoshi {\n\tvar sourceLimit lnwire.MilliSatoshi\n\tvar targetLimit lnwire.MilliSatoshi\n\n\tfor _, edge := range r.edgeLookup {\n\t\tif edge.from != r.source {\n\t\t\tcontinue\n\t\t}\n\n\t\tlimit := edge.publicCeiling()\n\t\tbalance, ok := r.localBalances[edge.chanID]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif balance < limit {\n\t\t\tlimit = balance\n\t\t}\n\t\tif limit > sourceLimit {\n\t\t\tsourceLimit = limit\n\t\t}\n\t}\n\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tlimit := edge.publicCeiling()\n\t\tif limit > targetLimit {\n\t\t\ttargetLimit = limit\n\t\t}\n\t}\n\n\tif sourceLimit == 0 || targetLimit == 0 {\n\t\treturn 0\n\t}\n\tif targetLimit < sourceLimit {\n\t\treturn targetLimit\n\t}\n\treturn sourceLimit\n}\n\nfunc (r *candidateRouter) minimumParts(\n\ttotal lnwire.MilliSatoshi) int {\n\n\tceiling := r.maximumPublicShard()\n\tif ceiling <= 0 {\n\t\treturn 1\n\t}\n\tif total <= ceiling {\n\t\treturn 1\n\t}\n\n\treturn int((total-1)/ceiling + 1)\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tslots uint32) ([]*plannedRoute, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\n\tminParts := r.minimumParts(total)\n\tif minParts > int(slots) {\n\t\treturn nil, errors.New(\"public htlc limits exceed maximum parts\")\n\t}\n\n\tfeeBudget := r.remainingFeeBudget()\n\tvar best []*plannedRoute\n\tbestScore := math.Inf(1)\n\n\tfor parts := minParts; parts <= int(slots); parts++ {\n\t\tpatterns := r.allocationPatterns(total, parts)\n\t\tfor _, allocation := range patterns {\n\t\t\tplan, score, ok := r.tryAllocation(\n\t\t\t\tallocation, feeBudget,\n\t\t\t)\n\t\t\tif ok && score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) != 0 {\n\t\treturn best, nil\n\t}\n\n\tif slots <= 1 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\taverage := total / lnwire.MilliSatoshi(slots)\n\tif average <= 0 {\n\t\taverage = 1\n\t}\n\n\tcandidates := []lnwire.MilliSatoshi{\n\t\taverage,\n\t\taverage - average/4,\n\t\taverage / 2,\n\t\taverage / 4,\n\t}\n\n\tfor _, shard := range candidates {\n\t\tif shard < minFallbackShardMsat {\n\t\t\tcontinue\n\t\t}\n\n\t\tcap := feeBudget\n\t\tif feeBudget != lnwire.MaxMilliSatoshi {\n\t\t\tcap = proportionalBudget(feeBudget, shard, total)\n\t\t\tslack := feeBudget / 20\n\t\t\tif cap <= feeBudget-slack {\n\t\t\t\tcap += slack\n\t\t\t} else {\n\t\t\t\tcap = feeBudget\n\t\t\t}\n\t\t}\n\n\t\tpr, err := r.findRoute(shard, r.reserved, cap)\n\t\tif err == nil {\n\t\t\treturn []*plannedRoute{pr}, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc queuedAmount(plan []*plannedRoute) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, pr := range plan {\n\t\ttotal += pr.meta.delivered\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= maxRouterAttempts {\n\t\treturn nil, errors.New(\"routing attempt limit reached\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tused := r.successfulParts\n\tif inFlightHtlcs > used {\n\t\tused = inFlightHtlcs\n\t}\n\tif used >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts exhausted\")\n\t}\n\tslots := maxParts - used\n\n\tif len(r.plan) != 0 && queuedAmount(r.plan) != amt {\n\t\tr.plan = nil\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tplan, err := r.makePlan(amt, slots)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.plan = plan\n\t}\n\n\tnext := r.plan[0]\n\tr.plan = r.plan[1:]\n\tr.issued[next.rt] = next.meta\n\tr.attempts++\n\n\treturn next.rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(rt *route.Route) *routeMeta {\n\tif meta, ok := r.issued[rt]; ok {\n\t\treturn meta\n\t}\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tloads := make([]lnwire.MilliSatoshi, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeLookup[key]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\tif reserved > lnwire.MaxMilliSatoshi-amount {\n\t\t\treturn nil\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tamounts = append(amounts, amount)\n\t\tloads = append(loads, amount+reserved)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\treturn &routeMeta{\n\t\tpath: path,\n\t\tamounts: amounts,\n\t\tloads: loads,\n\t\tdelivered: delivered,\n\t\tfee: rt.TotalAmount - delivered,\n\t}\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key edgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tfailure := r.localFailures[key]\n\tif failure.upperBad == 0 || amount < failure.upperBad {\n\t\tfailure.upperBad = amount\n\t}\n\tfailure.failCount++\n\n\tcooldown := uint32(6) + failure.failCount*3\n\tif cooldown > 24 {\n\t\tcooldown = 24\n\t}\n\tfailure.retryAt = r.attempts + cooldown\n\tr.localFailures[key] = failure\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tmeta := r.metaForRoute(rt)\n\tdelete(r.issued, rt)\n\tif meta == nil {\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tnow := r.view.Now()\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range meta.path {\n\t\t\tupdateBelief(edge.key, meta.loads[i], true, now)\n\t\t\tr.reserved[edge.key] += meta.amounts[i]\n\t\t\tr.edgePenalty[edge.key] *= 0.35\n\t\t\tdelete(r.localFailures, edge.key)\n\t\t}\n\n\t\tif meta.fee <= lnwire.MaxMilliSatoshi-r.committedFees {\n\t\t\tr.committedFees += meta.fee\n\t\t}\n\t\tr.successfulParts++\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tpassed := failIdx\n\tif passed < 0 {\n\t\tpassed = 0\n\t}\n\tif passed > len(meta.path) {\n\t\tpassed = len(meta.path)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tupdateBelief(meta.path[i].key, meta.loads[i], true, now)\n\t}\n\n\tcode := result.Failure.Code()\n\n\tif failIdx >= 0 && failIdx < len(meta.path) {\n\t\tedge := meta.path[failIdx]\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tupdateBelief(edge.key, meta.loads[failIdx], false, now)\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\t\t\tr.edgePenalty[edge.key] += 180_000\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.policyBad[edge.key] = true\n\t\t\tr.edgePenalty[edge.key] += 5_000_000\n\n\t\tdefault:\n\t\t\tr.recordLocalFailure(edge.key, meta.loads[failIdx])\n\t\t\tr.edgePenalty[edge.key] += 1_500_000\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif failIdx == len(meta.path) && len(meta.path) != 0 {\n\t\tlast := meta.path[len(meta.path)-1]\n\t\tr.edgePenalty[last.key] += 750_000\n\t\treturn nil\n\t}\n\n\tif r.spec.FeeLimitMsat != lnwire.MaxMilliSatoshi {\n\t\tguard := meta.fee / 20\n\t\tif guard < 1_000 {\n\t\t\tguard = 1_000\n\t\t}\n\t\tif guard <= lnwire.MaxMilliSatoshi-r.budgetGuard {\n\t\t\tr.budgetGuard += guard\n\t\t}\n\t}\n\tfor _, edge := range meta.path {\n\t\tr.edgePenalty[edge.key] += 100_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 38,
"parent": 2,
"score": 0.45,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCLTV = uint32(40)\n\tcandidateRiskPrice = 420000.0\n\tcandidateHopPrice = 8000.0\n\tcandidateReuseNats = 1.20\n\tcandidateMinShard = lnwire.MilliSatoshi(10000)\n\tcandidateProbFloor = 0.012\n\tcandidateProbCeiling = 0.995\n\tcandidateMaxLabels = 8\n\tcandidateMaxHops = 40\n\tcandidatePlanNodes = 120\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 int64\n\tfeeRatePPM int64\n\tcltvDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n\n\tinboundBase int64\n\tinboundRate int64\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\nfunc (e *candidateEdge) outgoingFee(amt lnwire.MilliSatoshi) int64 {\n\tfee := e.baseFeeMsat + int64(amt)*e.feeRatePPM/1_000_000\n\tif fee < 0 {\n\t\treturn 0\n\t}\n\treturn fee\n}\n\nfunc candidateForwardingFee(incoming, outgoing *candidateEdge,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif incoming == nil || outgoing == nil {\n\t\treturn 0\n\t}\n\n\toutFee := outgoing.outgoingFee(amt)\n\tinBasis := int64(amt) + outFee\n\tinFee := incoming.inboundBase +\n\t\tinBasis*incoming.inboundRate/1_000_000\n\n\ttotal := outFee + inFee\n\tif total < 0 {\n\t\ttotal = 0\n\t}\n\treturn lnwire.MilliSatoshi(total)\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tsuccess uint32\n\tfailure uint32\n}\n\nvar candidateSharedBeliefs = struct {\n\tsync.Mutex\n\tm map[candidateEdgeKey]candidateBelief\n}{\n\tm: make(map[candidateEdgeKey]candidateBelief),\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn candidateProbFloor\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.045)\n\thighMode := 0.5 / (1 + math.Exp((x-0.94)/0.035))\n\tp := lowMode + highMode\n\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc candidateProbability(e *candidateEdge,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tprior := candidatePrior(load, e.capacity)\n\n\tcandidateSharedBeliefs.Lock()\n\tbelief, ok := candidateSharedBeliefs.m[e.key]\n\tcandidateSharedBeliefs.Unlock()\n\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && load <= belief.lowerOK {\n\t\treturn candidateProbCeiling\n\t}\n\n\tif belief.upperBad > 0 && load >= belief.upperBad {\n\t\tp := prior * 0.08\n\t\tif p < candidateProbFloor {\n\t\t\tp = candidateProbFloor\n\t\t}\n\t\treturn p\n\t}\n\n\tvar learned, confidence float64\n\n\tswitch {\n\tcase belief.lowerOK > 0 && belief.upperBad > belief.lowerOK:\n\t\tmid := 0.5 * float64(belief.lowerOK+belief.upperBad)\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad-belief.lowerOK)*0.18,\n\t\t\tfloat64(e.capacity)*0.012,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp((float64(load)-mid)/width))\n\t\tconfidence = 0.88\n\n\tcase belief.upperBad > 0:\n\t\twidth := math.Max(\n\t\t\tfloat64(belief.upperBad)*0.14,\n\t\t\tfloat64(e.capacity)*0.015,\n\t\t)\n\t\tlearned = 1 / (1 + math.Exp(\n\t\t\t(float64(load)-float64(belief.upperBad))/width,\n\t\t))\n\t\tconfidence = 0.72\n\n\tcase belief.lowerOK > 0:\n\t\texcess := float64(load-belief.lowerOK) /\n\t\t\tmath.Max(float64(e.capacity), 1)\n\t\tlearned = candidateProbCeiling * math.Exp(-excess/0.12)\n\t\tconfidence = 0.62\n\n\tdefault:\n\t\treturn prior\n\t}\n\n\tp := confidence*learned + (1-confidence)*prior\n\tif p < candidateProbFloor {\n\t\treturn candidateProbFloor\n\t}\n\tif p > candidateProbCeiling {\n\t\treturn candidateProbCeiling\n\t}\n\treturn p\n}\n\nfunc candidateRecordSuccess(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif load > b.lowerOK {\n\t\tb.lowerOK = load\n\t}\n\tb.success++\n\n\tif b.upperBad > 0 && b.lowerOK >= b.upperBad {\n\t\tb.upperBad = b.lowerOK + 1\n\t}\n\n\tcandidateSharedBeliefs.m[key] = b\n}\n\nfunc candidateRecordFailure(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\tcandidateSharedBeliefs.Lock()\n\tdefer candidateSharedBeliefs.Unlock()\n\n\tb := candidateSharedBeliefs.m[key]\n\tif b.upperBad == 0 || load < b.upperBad {\n\t\tb.upperBad = load\n\t}\n\tb.failure++\n\tcandidateSharedBeliefs.m[key] = b\n}\n\ntype candidateSearchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk float64\n\thops int\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype candidateSearchQueue []*candidateSearchItem\n\nfunc (q candidateSearchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateSearchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateSearchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateSearchQueue) Push(x any) {\n\titem := x.(*candidateSearchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateSearchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype candidateLabel struct {\n\tscore float64\n\tamount lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocal map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route]bool\n\tpenalty map[candidateEdgeKey]float64\n\tbanned map[candidateEdgeKey]bool\n\n\tfailedLoad map[candidateEdgeKey]lnwire.MilliSatoshi\n\tfailureCount map[candidateEdgeKey]uint32\n\tfailedRoutes map[uint64]uint32\n\n\tplan []*route.Route\n\tcommittedFees lnwire.MilliSatoshi\n\tfailures 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[candidateEdgeKey]*candidateEdge),\n\t\tlocal: localBalances,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route]bool),\n\t\tpenalty: make(map[candidateEdgeKey]float64),\n\t\tbanned: make(map[candidateEdgeKey]bool),\n\t\tfailedLoad: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tfailureCount: make(map[candidateEdgeKey]uint32),\n\t\tfailedRoutes: make(map[uint64]uint32),\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\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\n\t\t\t\t\tbaseFeeMsat: int64(policy.FeeBaseMSat),\n\t\t\t\t\tfeeRatePPM: int64(\n\t\t\t\t\t\tpolicy.FeeProportionalMillionths,\n\t\t\t\t\t),\n\t\t\t\t\tcltvDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\n\t\t\t\t\tinboundBase: int64(ch.InboundFee.BaseFee),\n\t\t\t\t\tinboundRate: int64(ch.InboundFee.FeeRate),\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\tr.edges[key] = edge\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateAdd(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b > 0 && a > lnwire.MaxMilliSatoshi-b {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\treturn a + b\n}\n\nfunc (r *candidateRouter) budgetLeft() lnwire.MilliSatoshi {\n\tif r.spec.FeeLimitMsat == lnwire.MaxMilliSatoshi {\n\t\treturn lnwire.MaxMilliSatoshi\n\t}\n\tif r.committedFees >= r.spec.FeeLimitMsat {\n\t\treturn 0\n\t}\n\treturn r.spec.FeeLimitMsat - r.committedFees\n}\n\nfunc (r *candidateRouter) routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tconst (\n\t\toffset = uint64(1469598103934665603)\n\t\tprime = uint64(1099511628211)\n\t)\n\n\th := offset\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= prime\n\t}\n\th ^= uint64(candidateRouteAmount(rt) / 10000)\n\th *= prime\n\treturn h\n}\n\nfunc candidateRouteAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidatePathContains(path []*candidateEdge, node route.Vertex) bool {\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAddLabel(labels map[route.Vertex][]candidateLabel,\n\tnode route.Vertex, score float64,\n\tamount lnwire.MilliSatoshi) bool {\n\n\tcurrent := labels[node]\n\tfor _, label := range current {\n\t\tif label.score <= score && label.amount <= amount {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfiltered := current[:0]\n\tfor _, label := range current {\n\t\tif score <= label.score && amount <= label.amount {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, label)\n\t}\n\n\tfiltered = append(filtered, candidateLabel{\n\t\tscore: score,\n\t\tamount: amount,\n\t})\n\n\tsort.Slice(filtered, func(i, j int) bool {\n\t\treturn filtered[i].score < filtered[j].score\n\t})\n\tif len(filtered) > candidateMaxLabels {\n\t\tfiltered = filtered[:candidateMaxLabels]\n\t}\n\n\tlabels[node] = filtered\n\treturn true\n}\n\nfunc (r *candidateRouter) liquidityPenalty(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) float64 {\n\n\tfailed := r.failedLoad[key]\n\tif failed <= 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(load) / math.Max(float64(failed), 1)\n\tcount := float64(r.failureCount[key])\n\textra := math.Min(count*0.08, 0.40)\n\n\tswitch {\n\tcase ratio >= 1:\n\t\treturn 2.25 + extra\n\tcase ratio >= 0.88:\n\t\treturn 1.25 + extra\n\tcase ratio >= 0.72:\n\t\treturn 0.48 + extra/2\n\tcase ratio >= 0.58:\n\t\treturn 0.14\n\tdefault:\n\t\treturn 0\n\t}\n}\n\nfunc (r *candidateRouter) findRoute(deliver lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tmaxFee lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid shard amount\")\n\t}\n\n\tlabels := make(map[route.Vertex][]candidateLabel)\n\tlabels[r.spec.Target] = []candidateLabel{{}}\n\n\tpq := &candidateSearchQueue{}\n\theap.Push(pq, &candidateSearchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t})\n\n\tvar fallback *route.Route\n\tvar fallbackQuality float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateSearchItem)\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item.path, deliver)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif r.routeFee(rt) > maxFee {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tquality := 1.0\n\t\t\tif item.hops > 0 {\n\t\t\t\tquality = math.Exp(-item.risk / float64(item.hops))\n\t\t\t}\n\n\t\t\tif r.failedRoutes[candidateRouteHash(rt)] == 0 {\n\t\t\t\treturn rt, quality, nil\n\t\t\t}\n\t\t\tif fallback == nil {\n\t\t\t\tfallback = rt\n\t\t\t\tfallbackQuality = quality\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.hops >= candidateMaxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar outgoing *candidateEdge\n\t\tif len(item.path) > 0 {\n\t\t\toutgoing = item.path[0]\n\t\t}\n\n\t\tfor _, incoming := range r.incoming[item.node] {\n\t\t\tif r.banned[incoming.key] ||\n\t\t\t\tcandidatePathContains(item.path, incoming.key.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOver := item.amount\n\t\t\tif item.node != r.spec.Target {\n\t\t\t\tamountOver = candidateAdd(\n\t\t\t\t\tamountOver,\n\t\t\t\t\tcandidateForwardingFee(\n\t\t\t\t\t\tincoming, outgoing, item.amount,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif !incoming.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif maxFee != lnwire.MaxMilliSatoshi &&\n\t\t\t\tamountOver-deliver > maxFee {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tload := candidateAdd(\n\t\t\t\tamountOver, r.reserved[incoming.key],\n\t\t\t)\n\t\t\tif extra != nil {\n\t\t\t\tload = candidateAdd(load, extra[incoming.key])\n\t\t\t}\n\t\t\tif load > incoming.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar probability float64\n\t\t\tif incoming.key.from == r.source {\n\t\t\t\tbalance, ok := r.local[incoming.key.chanID]\n\t\t\t\tif !ok || load > balance {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprobability = 0.999\n\t\t\t} else {\n\t\t\t\tprobability = candidateProbability(incoming, load)\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability)\n\t\t\tedgePenalty := r.penalty[incoming.key] +\n\t\t\t\tr.liquidityPenalty(incoming.key, load)\n\n\t\t\treuse := 0.0\n\t\t\tif r.reserved[incoming.key] > 0 ||\n\t\t\t\t(extra != nil && extra[incoming.key] > 0) {\n\n\t\t\t\treuse = candidateReuseNats\n\t\t\t}\n\n\t\t\tfeeIncrement := float64(amountOver - item.amount)\n\t\t\tnewScore := item.score + feeIncrement +\n\t\t\t\tcandidateRiskPrice*(risk+edgePenalty+reuse) +\n\t\t\t\tcandidateHopPrice\n\n\t\t\tif incoming.key.from != r.source &&\n\t\t\t\t!candidateAddLabel(\n\t\t\t\t\tlabels, incoming.key.from,\n\t\t\t\t\tnewScore, amountOver,\n\t\t\t\t) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = incoming\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\theap.Push(pq, &candidateSearchItem{\n\t\t\t\tnode: incoming.key.from,\n\t\t\t\tamount: amountOver,\n\t\t\t\tscore: newScore,\n\t\t\t\trisk: item.risk + risk,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\tif fallback != nil {\n\t\treturn fallback, fallbackQuality, nil\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tdeliver lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tn := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, n)\n\texpiries := make([]uint32, n)\n\n\tamounts[n-1] = deliver\n\texpiries[n-1] = candidateFinalCLTV\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tfee := candidateForwardingFee(\n\t\t\tpath[i], path[i+1], amounts[i+1],\n\t\t)\n\t\tamounts[i] = candidateAdd(amounts[i+1], fee)\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].cltvDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := candidateFinalCLTV\n\t\tif i < n-1 {\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) forEachRouteEdge(rt *route.Route,\n\tfn func(candidateEdgeKey, lnwire.MilliSatoshi, int)) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tfn(key, amount, i)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc (r *candidateRouter) addVirtualReservation(\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\treserved[key] = candidateAdd(reserved[key], amount)\n\t})\n}\n\nfunc candidateCloneReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tclone := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tclone[key] = amount\n\t}\n\treturn clone\n}\n\nfunc (r *candidateRouter) reserveIssued(rt *route.Route) {\n\tif r.issued[rt] {\n\t\treturn\n\t}\n\n\tr.issued[rt] = true\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tr.reserved[key] = candidateAdd(r.reserved[key], amount)\n\t})\n}\n\nfunc (r *candidateRouter) releaseIssued(rt *route.Route) {\n\tif !r.issued[rt] {\n\t\treturn\n\t}\n\n\tdelete(r.issued, rt)\n\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\tcurrent := r.reserved[key]\n\t\tif current <= amount {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = current - amount\n\t\t}\n\t})\n}\n\ntype candidatePlanChoice struct {\n\tamount lnwire.MilliSatoshi\n\troute *route.Route\n\tquality float64\n\tmetric float64\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamount, minimum, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amount < minimum || amount > maximum {\n\t\treturn amounts\n\t}\n\tfor _, existing := range amounts {\n\t\tif existing == amount {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amount)\n}\n\nfunc (r *candidateRouter) partFeeLimit(planned lnwire.MilliSatoshi,\n\tparts int) lnwire.MilliSatoshi {\n\n\tleft := r.budgetLeft()\n\tif left == lnwire.MaxMilliSatoshi {\n\t\treturn left\n\t}\n\tif planned >= left {\n\t\treturn 0\n\t}\n\n\tleft -= planned\n\tif parts <= 2 {\n\t\treturn left\n\t}\n\n\tshare := left / lnwire.MilliSatoshi(parts)\n\treturn candidateAdd(share, share)\n}\n\nfunc (r *candidateRouter) planSearch(\n\tremaining lnwire.MilliSatoshi,\n\tparts int,\n\tvirtual map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tplannedFees lnwire.MilliSatoshi,\n\tnodes *int) ([]*route.Route, bool) {\n\n\t*nodes++\n\tif *nodes > candidatePlanNodes || parts < 1 {\n\t\treturn nil, false\n\t}\n\n\tif parts == 1 {\n\t\trt, _, err := r.findRoute(\n\t\t\tremaining, virtual,\n\t\t\tr.partFeeLimit(plannedFees, parts),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tleft := r.budgetLeft()\n\t\tif left != lnwire.MaxMilliSatoshi &&\n\t\t\tplannedFees+fee > left {\n\n\t\t\treturn nil, false\n\t\t}\n\n\t\treturn []*route.Route{rt}, true\n\t}\n\n\tminimum := candidateMinShard\n\tmaximum := remaining -\n\t\tminimum*lnwire.MilliSatoshi(parts-1)\n\tif maximum < minimum {\n\t\treturn nil, false\n\t}\n\n\taverage := remaining / lnwire.MilliSatoshi(parts)\n\tamounts := make([]lnwire.MilliSatoshi, 0, 7)\n\tamounts = candidateAppendAmount(\n\t\tamounts, average, minimum, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, average*4/5, minimum, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, average*2/3, minimum, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, average/2, minimum, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, average*5/4, minimum, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, average*3/2, minimum, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, maximum, minimum, maximum,\n\t)\n\n\tchoices := make([]candidatePlanChoice, 0, len(amounts))\n\tmaxFee := r.partFeeLimit(plannedFees, parts)\n\n\tfor _, amount := range amounts {\n\t\trt, quality, err := r.findRoute(amount, virtual, maxFee)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tleft := r.budgetLeft()\n\t\tif left != lnwire.MaxMilliSatoshi &&\n\t\t\tplannedFees+fee > left {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif quality < candidateProbFloor {\n\t\t\tquality = candidateProbFloor\n\t\t}\n\n\t\tbalanceCost := 0.55 * math.Abs(math.Log(\n\t\t\tfloat64(amount) / math.Max(float64(average), 1),\n\t\t))\n\t\tmetric := -math.Log(quality) + balanceCost +\n\t\t\tfloat64(fee)/candidateRiskPrice\n\n\t\tif r.failures > 0 && amount >= average {\n\t\t\tmetric += 0.08\n\t\t}\n\n\t\tchoices = append(choices, candidatePlanChoice{\n\t\t\tamount: amount,\n\t\t\troute: rt,\n\t\t\tquality: quality,\n\t\t\tmetric: metric,\n\t\t})\n\t}\n\n\tsort.Slice(choices, func(i, j int) bool {\n\t\treturn choices[i].metric < choices[j].metric\n\t})\n\n\tfor _, choice := range choices {\n\t\tnextVirtual := candidateCloneReservations(virtual)\n\t\tr.addVirtualReservation(nextVirtual, choice.route)\n\n\t\trest, ok := r.planSearch(\n\t\t\tremaining-choice.amount, parts-1,\n\t\t\tnextVirtual,\n\t\t\tplannedFees+r.routeFee(choice.route),\n\t\t\tnodes,\n\t\t)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tplan := make([]*route.Route, 1, len(rest)+1)\n\t\tplan[0] = choice.route\n\t\tplan = append(plan, rest...)\n\t\treturn plan, true\n\t}\n\n\treturn nil, false\n}\n\nfunc (r *candidateRouter) tryPlan(amount lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, bool) {\n\n\tif parts < 1 ||\n\t\t(parts > 1 &&\n\t\t\tamount < candidateMinShard*lnwire.MilliSatoshi(parts)) {\n\n\t\treturn nil, false\n\t}\n\n\tnodes := 0\n\treturn r.planSearch(\n\t\tamount, parts,\n\t\tmake(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\t0, &nodes,\n\t)\n}\n\nfunc (r *candidateRouter) makePlan(amount lnwire.MilliSatoshi,\n\tslots uint32) ([]*route.Route, error) {\n\n\tif slots == 0 {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tfull, quality, fullErr := r.findRoute(\n\t\tamount, nil, r.budgetLeft(),\n\t)\n\n\tif slots == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tpreferred := 1\n\tswitch {\n\tcase fullErr != nil:\n\t\tpreferred = 2\n\tcase quality < 0.40:\n\t\tpreferred = 3\n\tcase quality < 0.62 && amount >= 50_000_000:\n\t\tpreferred = 2\n\t}\n\n\tif r.failures > 0 {\n\t\tretryParts := 1 + int(r.failures)\n\t\tif retryParts < 2 {\n\t\t\tretryParts = 2\n\t\t}\n\t\tif retryParts > preferred {\n\t\t\tpreferred = retryParts\n\t\t}\n\t}\n\n\tif preferred > int(slots) {\n\t\tpreferred = int(slots)\n\t}\n\n\tif preferred == 1 && fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tfor parts := preferred; parts <= int(slots); parts++ {\n\t\tplan, ok := r.tryPlan(amount, parts)\n\t\tif ok {\n\t\t\treturn plan, nil\n\t\t}\n\t}\n\n\tif fullErr == nil {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tshard := amount / lnwire.MilliSatoshi(slots)\n\tif shard < candidateMinShard {\n\t\tshard = candidateMinShard\n\t}\n\n\tfor shard >= candidateMinShard {\n\t\trt, _, err := r.findRoute(\n\t\t\tshard, nil, r.budgetLeft(),\n\t\t)\n\t\tif err == nil {\n\t\t\treturn []*route.Route{rt}, nil\n\t\t}\n\t\tif shard == candidateMinShard {\n\t\t\tbreak\n\t\t}\n\n\t\tshard = shard * 2 / 3\n\t\tif shard < candidateMinShard {\n\t\t\tshard = candidateMinShard\n\t\t}\n\t}\n\n\treturn nil, fullErr\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tfor {\n\t\tif len(r.plan) == 0 {\n\t\t\tmaxParts := r.spec.MaxParts\n\t\t\tif maxParts == 0 {\n\t\t\t\tmaxParts = 1\n\t\t\t}\n\t\t\tif inFlightHtlcs >= maxParts {\n\t\t\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t\t\t}\n\n\t\t\tplan, err := r.makePlan(\n\t\t\t\tamt, maxParts-inFlightHtlcs,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr.plan = plan\n\t\t}\n\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\n\t\tif r.routeAmount(rt) > amt {\n\t\t\tr.plan = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := r.routeFee(rt)\n\t\tbudget := r.budgetLeft()\n\t\tif budget != lnwire.MaxMilliSatoshi && fee > budget {\n\t\t\tr.plan = nil\n\t\t\tr.failures++\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserveIssued(rt)\n\t\treturn rt, nil\n\t}\n}\n\nfunc (r *candidateRouter) routeFailureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) routeEdgeAt(rt *route.Route,\n\tindex int) (candidateEdgeKey, lnwire.MilliSatoshi, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tamount := rt.TotalAmount\n\tif index > 0 {\n\t\tamount = rt.Hops[index-1].AmtToForward\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, amount, true\n}\n\nfunc (r *candidateRouter) observedLoad(key candidateEdgeKey,\n\tfallback lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif load := r.reserved[key]; load > 0 {\n\t\treturn load\n\t}\n\treturn fallback\n}\n\nfunc (r *candidateRouter) recordLocalFailure(key candidateEdgeKey,\n\tload lnwire.MilliSatoshi) {\n\n\told := r.failedLoad[key]\n\tif old == 0 || load < old {\n\t\tr.failedLoad[key] = load\n\t}\n\tr.failureCount[key]++\n\tcandidateRecordFailure(key, load)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\tamount lnwire.MilliSatoshi, _ int) {\n\n\t\t\tload := r.observedLoad(key, amount)\n\t\t\tcandidateRecordSuccess(key, load)\n\n\t\t\tif failed := r.failedLoad[key];\n\t\t\t\tfailed > 0 && load >= failed {\n\n\t\t\t\tdelete(r.failedLoad, key)\n\t\t\t\tdelete(r.failureCount, key)\n\t\t\t}\n\t\t\tif r.penalty[key] > 0.25 {\n\t\t\t\tr.penalty[key] -= 0.25\n\t\t\t} else {\n\t\t\t\tdelete(r.penalty, key)\n\t\t\t}\n\t\t})\n\n\t\tr.committedFees = candidateAdd(\n\t\t\tr.committedFees, r.routeFee(rt),\n\t\t)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tr.plan = nil\n\tr.failedRoutes[candidateRouteHash(rt)]++\n\n\tfailIndex := r.routeFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tfor i := 0; i < failIndex && i < len(rt.Hops); i++ {\n\t\tkey, amount, ok := r.routeEdgeAt(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tload := r.observedLoad(key, amount)\n\t\tcandidateRecordSuccess(key, load)\n\t}\n\n\tcode := result.Failure.Code()\n\tif key, amount, ok := r.routeEdgeAt(rt, failIndex); ok {\n\t\tload := r.observedLoad(key, amount)\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tr.recordLocalFailure(key, load)\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tr.banned[key] = true\n\n\t\tdefault:\n\t\t\tr.penalty[key] += 0.85\n\t\t}\n\t} else {\n\t\tpenalty := 0.35\n\t\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\t\tpenalty = 0.62\n\t\t}\n\n\t\tr.forEachRouteEdge(rt, func(key candidateEdgeKey,\n\t\t\t_ lnwire.MilliSatoshi, _ int) {\n\n\t\t\tr.penalty[key] += penalty\n\t\t})\n\t}\n\n\tr.releaseIssued(rt)\n\treturn nil\n}"
}
}
]
}