mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-18 13:07:58 +02:00
184 lines
No EOL
240 KiB
JSON
184 lines
No EOL
240 KiB
JSON
{
|
|
"run_id": "code_split2",
|
|
"reflection_lm": "codex:gpt-5.6-sol",
|
|
"mode": "generalization",
|
|
"status": "complete",
|
|
"seed_score": 0.6293,
|
|
"best_score": 0.9604,
|
|
"iterations": [
|
|
{
|
|
"i": 0,
|
|
"candidate_score": 0.6293,
|
|
"best_score": 0.6293,
|
|
"note": "seed"
|
|
},
|
|
{
|
|
"i": 1,
|
|
"candidate_score": 0.7726,
|
|
"best_score": 0.7726,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 2,
|
|
"candidate_score": 0.7835,
|
|
"best_score": 0.7726,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 3,
|
|
"candidate_score": 0.5285,
|
|
"best_score": 0.7726,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 4,
|
|
"candidate_score": -0.15,
|
|
"best_score": 0.7726,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 5,
|
|
"candidate_score": 0.0,
|
|
"best_score": 0.7726,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 6,
|
|
"candidate_score": 0.7947,
|
|
"best_score": 0.7947,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 7,
|
|
"candidate_score": 0.5021,
|
|
"best_score": 0.7947,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 8,
|
|
"candidate_score": 0.5085,
|
|
"best_score": 0.7947,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 9,
|
|
"candidate_score": -0.1122,
|
|
"best_score": 0.7947,
|
|
"note": "rejected"
|
|
}
|
|
],
|
|
"seed_params": {
|
|
"source": "package main\n\n// This file is the CANDIDATE SLOT for evolved routing algorithms. During\n// optimization, the entire file is replaced (via go build -overlay) with a\n// generated implementation. The contract is a single constructor:\n//\n//\tnewCandidateRouter(view, source, localBalances, spec)\n//\n// returning a routing.SimRouter. The router sees only the public gossip\n// graph, its own channel balances and per-attempt feedback \u2014 the same\n// information a real Lightning sender has. The in-tree implementation below\n// is the seed algorithm: a deliberately simple fee-optimizing Dijkstra with\n// failure blacklisting and halving-based MPP splitting.\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\n// candidateEdge is one directed edge of the public graph: a channel from\n// one node to another, with the policy the sending node announced.\ntype candidateEdge struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\n// fee returns the fee the sending node charges to forward amt over this\n// edge.\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\n// usable reports whether the edge can carry the given amount per its\n// announced policy.\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\t// The public capacity is a hard upper bound on what can flow.\n\treturn amt <= e.capacity\n}\n\n// candidateRouter is the seed algorithm: cheapest-path routing with a\n// failure blacklist and amount halving when no route is found.\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\t// incomingEdges maps a node to the directed edges arriving at it,\n\t// the natural shape for backward Dijkstra.\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\n\t// localBalances is the exact outbound liquidity of our own channels.\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// failedAmt records, per directed channel, the lowest amount that\n\t// failed with a liquidity error; routes are built to stay below it.\n\tfailedAmt map[uint64]lnwire.MilliSatoshi\n\n\t// shardAmt is the current shard size for MPP splitting.\n\tshardAmt lnwire.MilliSatoshi\n\n\t// partsUsed counts the successful shards so far.\n\tpartsUsed uint32\n\n\t// pending maps in-flight attempt ids to their routes.\n\tpending map[uint64]*route.Route\n}\n\n// newCandidateRouter builds the router for one payment. This signature is\n// the stable contract between the harness and generated candidates.\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\trouter := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tfailedAmt: make(map[uint64]lnwire.MilliSatoshi),\n\t\tshardAmt: spec.Amount,\n\t\tpending: make(map[uint64]*route.Route),\n\t}\n\n\t// Build the adjacency list from gossip. Iterating a node's channels\n\t// yields, per channel, the policy the OTHER node announced toward us\n\t// (InPolicy). That is exactly the policy governing the directed edge\n\t// other -> node, so we record the reversed edge at each visit.\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(ctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\trouter.incomingEdges[edge.to] = append(\n\t\t\t\t\trouter.incomingEdges[edge.to], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn router, nil\n}\n\n// dijkstraItem is a priority queue entry.\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tcost lnwire.MilliSatoshi\n\tidx int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int { return len(q) }\nfunc (q dijkstraQueue) Less(i, j int) bool { return q[i].cost < q[j].cost }\nfunc (q dijkstraQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i]; q[i].idx = i; q[j].idx = j }\nfunc (q *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\n// findRoute computes the cheapest usable path delivering amt to the target,\n// walking backward from the target so fees accumulate correctly.\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (*route.Route,\n\terror) {\n\n\t// dist[node] = amount that must arrive at node to deliver amt.\n\tdist := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = amt\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{node: r.spec.Target, cost: amt})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode, arriving := item.node, item.cost\n\n\t\tif arriving > dist[node] {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\t// Consider all edges INTO node: for edge u->node, u must\n\t\t// send arriving plus u's fee.\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tamtOver := arriving\n\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Skip channels whose liquidity failure bound says\n\t\t\t// this amount cannot pass.\n\t\t\tif bound, ok := r.failedAmt[edge.chanID]; ok &&\n\t\t\t\tamtOver >= bound {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Our own channels: check exact local balance.\n\t\t\tif edge.from == r.source {\n\t\t\t\tif r.localBalances[edge.chanID] < amtOver {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar sending lnwire.MilliSatoshi\n\t\t\tif edge.from == r.source {\n\t\t\t\t// We pay no fee to ourselves.\n\t\t\t\tsending = amtOver\n\t\t\t} else {\n\t\t\t\tsending = amtOver + edge.fee(amtOver)\n\t\t\t}\n\n\t\t\tbest, ok := dist[edge.from]\n\t\t\tif !ok || sending < best {\n\t\t\t\tdist[edge.from] = sending\n\t\t\t\tnext[edge.from] = edge\n\t\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\t\tnode: edge.from,\n\t\t\t\t\tcost: sending,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\n// buildRoute walks the next-pointers from source to target and constructs a\n// route with correctly accumulated fees and cltv deltas.\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta = 40\n\n\t// Collect the path edges source -> target.\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\t// Amounts and expiries per channel, computed backward.\n\tnumHops := len(path)\n\tamtOver := make([]lnwire.MilliSatoshi, numHops)\n\texpiryOver := make([]uint32, numHops)\n\n\tamtOver[numHops-1] = amt\n\texpiryOver[numHops-1] = finalCltvDelta\n\n\tfor i := numHops - 2; i >= 0; i-- {\n\t\tfwd := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] + fwd.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(fwd.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, numHops)\n\tfor i, edge := range path {\n\t\tamtToFwd := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < numHops-1 {\n\t\t\tamtToFwd = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amtToFwd,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\n// RequestRoute returns the next route to try: the cheapest path for the\n// current shard size, halving the shard when no route exists.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif r.shardAmt > amt {\n\t\tr.shardAmt = amt\n\t}\n\n\tfor {\n\t\trt, err := r.findRoute(r.shardAmt)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\t// No route at this shard size: split if we're allowed more\n\t\t// parts and the shard is still meaningfully large.\n\t\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\t\tif partsLeft <= 1 || r.shardAmt < 10_000_000 {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.shardAmt /= 2\n\t}\n}\n\n// ReportAttempt learns from an attempt: liquidity failures set an upper\n// bound on the failing channel.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\treturn nil\n\t}\n\n\t// Locate the failing hop and record the amount bound on its\n\t// outgoing channel.\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\tfailIdx = i + 1\n\t\t}\n\t}\n\n\t// The failing node could not forward over its outgoing channel,\n\t// which is rt.Hops[failIdx].\n\tif failIdx >= 0 && failIdx < len(rt.Hops) {\n\t\thop := rt.Hops[failIdx]\n\t\tamtOver := rt.TotalAmount\n\t\tif failIdx > 0 {\n\t\t\tamtOver = rt.Hops[failIdx-1].AmtToForward\n\t\t}\n\n\t\tbound, ok := r.failedAmt[hop.ChannelID]\n\t\tif !ok || amtOver < bound {\n\t\t\tr.failedAmt[hop.ChannelID] = amtOver\n\t\t}\n\t}\n\n\treturn nil\n}\n"
|
|
},
|
|
"best_candidate": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\t// The first term models the small depleted-side tail. The second\n\t// models the large liquid-side mode and its cliff near capacity.\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.10\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.78, 0.22*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: deliver,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := item.amt + r.reserved[edge.key]\n\t\t\tif totalLiquidity > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotalLiquidity >\n\t\t\t\t\tr.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\t// Reliability dominates. A million-msat risk scale still\n\t\t\t// permits fees to break ties between similarly reliable paths.\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_000_000 +\n\t\t\t\t0.015 +\n\t\t\t\t0.32*float64(r.edgeFailures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 14)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\t// Failure bounds are useful shard breakpoints. Only retain the largest\n\t// few after deduplication to keep route search bounded.\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] || belief.upperFail <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tbound := belief.upperFail * 3 / 4\n\t\tif bound >= minimum && bound <= amt {\n\t\t\tbounds = append(bounds, bound)\n\t\t}\n\t}\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 4 {\n\t\tbounds = bounds[:4]\n\t}\n\tfor _, bound := range bounds {\n\t\taddCandidate(&values, seen, bound, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// This approximates joint route-set planning. It prices route\n\t\t// failure, fees, and the number of shards needed to cover the\n\t\t// remaining amount while reservations steer concurrent shards onto\n\t\t// distinct corridors.\n\t\tshardRatio := float64(amt) / float64(shard)\n\t\tchoice.utility = -math.Log(choice.probability) +\n\t\t\t0.30*math.Log(shardRatio) +\n\t\t\tfloat64(choice.fee)/2_000_000\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\n\n\tkeys := make([]edgeKey, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkeys[i] = edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 3\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + amt/3) / 3\n\t}\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\treturn value - amount\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\t// Settlement moves liquidity away from this direction, so shift all\n\t// evidence by the amount that just traversed the channel.\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, key := range keys {\n\t\tr.edgeFailures[key]++\n\t}\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\treturn nil\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// A route using an advertised policy that is rejected for fees or\n\t\t// timelocks should not be retried during this payment.\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
|
|
},
|
|
"stats": {
|
|
"evals_done": 89,
|
|
"distinct_candidates": 10
|
|
},
|
|
"candidates": [
|
|
{
|
|
"id": 0,
|
|
"parent": null,
|
|
"score": 0.6293,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"role": "seed",
|
|
"params": {
|
|
"source": "package main\n\n// This file is the CANDIDATE SLOT for evolved routing algorithms. During\n// optimization, the entire file is replaced (via go build -overlay) with a\n// generated implementation. The contract is a single constructor:\n//\n//\tnewCandidateRouter(view, source, localBalances, spec)\n//\n// returning a routing.SimRouter. The router sees only the public gossip\n// graph, its own channel balances and per-attempt feedback \u2014 the same\n// information a real Lightning sender has. The in-tree implementation below\n// is the seed algorithm: a deliberately simple fee-optimizing Dijkstra with\n// failure blacklisting and halving-based MPP splitting.\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\n// candidateEdge is one directed edge of the public graph: a channel from\n// one node to another, with the policy the sending node announced.\ntype candidateEdge struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\n// fee returns the fee the sending node charges to forward amt over this\n// edge.\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\n// usable reports whether the edge can carry the given amount per its\n// announced policy.\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\t// The public capacity is a hard upper bound on what can flow.\n\treturn amt <= e.capacity\n}\n\n// candidateRouter is the seed algorithm: cheapest-path routing with a\n// failure blacklist and amount halving when no route is found.\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\t// incomingEdges maps a node to the directed edges arriving at it,\n\t// the natural shape for backward Dijkstra.\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\n\t// localBalances is the exact outbound liquidity of our own channels.\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// failedAmt records, per directed channel, the lowest amount that\n\t// failed with a liquidity error; routes are built to stay below it.\n\tfailedAmt map[uint64]lnwire.MilliSatoshi\n\n\t// shardAmt is the current shard size for MPP splitting.\n\tshardAmt lnwire.MilliSatoshi\n\n\t// partsUsed counts the successful shards so far.\n\tpartsUsed uint32\n\n\t// pending maps in-flight attempt ids to their routes.\n\tpending map[uint64]*route.Route\n}\n\n// newCandidateRouter builds the router for one payment. This signature is\n// the stable contract between the harness and generated candidates.\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\trouter := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tfailedAmt: make(map[uint64]lnwire.MilliSatoshi),\n\t\tshardAmt: spec.Amount,\n\t\tpending: make(map[uint64]*route.Route),\n\t}\n\n\t// Build the adjacency list from gossip. Iterating a node's channels\n\t// yields, per channel, the policy the OTHER node announced toward us\n\t// (InPolicy). That is exactly the policy governing the directed edge\n\t// other -> node, so we record the reversed edge at each visit.\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(ctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\trouter.incomingEdges[edge.to] = append(\n\t\t\t\t\trouter.incomingEdges[edge.to], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn router, nil\n}\n\n// dijkstraItem is a priority queue entry.\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tcost lnwire.MilliSatoshi\n\tidx int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int { return len(q) }\nfunc (q dijkstraQueue) Less(i, j int) bool { return q[i].cost < q[j].cost }\nfunc (q dijkstraQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i]; q[i].idx = i; q[j].idx = j }\nfunc (q *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\n// findRoute computes the cheapest usable path delivering amt to the target,\n// walking backward from the target so fees accumulate correctly.\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (*route.Route,\n\terror) {\n\n\t// dist[node] = amount that must arrive at node to deliver amt.\n\tdist := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = amt\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{node: r.spec.Target, cost: amt})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode, arriving := item.node, item.cost\n\n\t\tif arriving > dist[node] {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\t// Consider all edges INTO node: for edge u->node, u must\n\t\t// send arriving plus u's fee.\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tamtOver := arriving\n\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Skip channels whose liquidity failure bound says\n\t\t\t// this amount cannot pass.\n\t\t\tif bound, ok := r.failedAmt[edge.chanID]; ok &&\n\t\t\t\tamtOver >= bound {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Our own channels: check exact local balance.\n\t\t\tif edge.from == r.source {\n\t\t\t\tif r.localBalances[edge.chanID] < amtOver {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar sending lnwire.MilliSatoshi\n\t\t\tif edge.from == r.source {\n\t\t\t\t// We pay no fee to ourselves.\n\t\t\t\tsending = amtOver\n\t\t\t} else {\n\t\t\t\tsending = amtOver + edge.fee(amtOver)\n\t\t\t}\n\n\t\t\tbest, ok := dist[edge.from]\n\t\t\tif !ok || sending < best {\n\t\t\t\tdist[edge.from] = sending\n\t\t\t\tnext[edge.from] = edge\n\t\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\t\tnode: edge.from,\n\t\t\t\t\tcost: sending,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\n// buildRoute walks the next-pointers from source to target and constructs a\n// route with correctly accumulated fees and cltv deltas.\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta = 40\n\n\t// Collect the path edges source -> target.\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\t// Amounts and expiries per channel, computed backward.\n\tnumHops := len(path)\n\tamtOver := make([]lnwire.MilliSatoshi, numHops)\n\texpiryOver := make([]uint32, numHops)\n\n\tamtOver[numHops-1] = amt\n\texpiryOver[numHops-1] = finalCltvDelta\n\n\tfor i := numHops - 2; i >= 0; i-- {\n\t\tfwd := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] + fwd.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(fwd.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, numHops)\n\tfor i, edge := range path {\n\t\tamtToFwd := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < numHops-1 {\n\t\t\tamtToFwd = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amtToFwd,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\n// RequestRoute returns the next route to try: the cheapest path for the\n// current shard size, halving the shard when no route exists.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif r.shardAmt > amt {\n\t\tr.shardAmt = amt\n\t}\n\n\tfor {\n\t\trt, err := r.findRoute(r.shardAmt)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\t// No route at this shard size: split if we're allowed more\n\t\t// parts and the shard is still meaningfully large.\n\t\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\t\tif partsLeft <= 1 || r.shardAmt < 10_000_000 {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.shardAmt /= 2\n\t}\n}\n\n// ReportAttempt learns from an attempt: liquidity failures set an upper\n// bound on the failing channel.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\treturn nil\n\t}\n\n\t// Locate the failing hop and record the amount bound on its\n\t// outgoing channel.\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\tfailIdx = i + 1\n\t\t}\n\t}\n\n\t// The failing node could not forward over its outgoing channel,\n\t// which is rt.Hops[failIdx].\n\tif failIdx >= 0 && failIdx < len(rt.Hops) {\n\t\thop := rt.Hops[failIdx]\n\t\tamtOver := rt.TotalAmount\n\t\tif failIdx > 0 {\n\t\t\tamtOver = rt.Hops[failIdx-1].AmtToForward\n\t\t}\n\n\t\tbound, ok := r.failedAmt[hop.ChannelID]\n\t\tif !ok || amtOver < bound {\n\t\t\tr.failedAmt[hop.ChannelID] = amtOver\n\t\t}\n\t}\n\n\treturn nil\n}\n"
|
|
}
|
|
},
|
|
{
|
|
"id": 1,
|
|
"parent": 0,
|
|
"score": 0.7726,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.68\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 250_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 50_000_000:\n\t\tparts = 2\n\t}\n\tif spec.MaxParts < parts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(spec.Amount, lnwire.MilliSatoshi(parts))\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tavailable := r.localBalances[edge.key.chanID]\n\t\tif reserved := r.reserved[edge.key]; reserved < available {\n\t\t\tavailable -= reserved\n\t\t} else {\n\t\t\tavailable = 0\n\t\t}\n\t\tif amt > available {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.08, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.55*prior + 0.45*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tif probability <= minProbability &&\n\t\t\t\tr.beliefs[edge.key].upperBad != 0 {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t} else {\n\t\t\t\tavailable := r.localBalances[edge.key.chanID]\n\t\t\t\tif reserved < available {\n\t\t\t\t\tavailable -= reserved\n\t\t\t\t} else {\n\t\t\t\t\tavailable = 0\n\t\t\t\t}\n\t\t\t\tif sending > available {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tr.reserved[key] += routeAmount(rt, i)\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tshard := r.baseShard\n\tif shard <= 0 || shard > amt {\n\t\tshard = amt\n\t}\n\n\tminNeeded := ceilDiv(amt, lnwire.MilliSatoshi(partsLeft))\n\tif shard < minNeeded {\n\t\tshard = minNeeded\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tminShard := lnwire.MilliSatoshi(10_000)\n\tif amt < minShard {\n\t\tminShard = amt\n\t}\n\n\tvar lastErr error\n\tfor shard >= minShard {\n\t\trt, err := r.findRoute(shard)\n\t\tif err == nil {\n\t\t\tr.reserve(rt)\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\n\t\tnextShard := lnwire.MilliSatoshi(\n\t\t\tfloat64(shard) * lowerRetryFactor,\n\t\t)\n\t\tif nextShard >= shard {\n\t\t\tnextShard = shard - 1\n\t\t}\n\t\tif nextShard < minNeeded {\n\t\t\tbreak\n\t\t}\n\t\tshard = nextShard\n\t}\n\n\tif shard != minNeeded {\n\t\trt, err := r.findRoute(minNeeded)\n\t\tif err == nil {\n\t\t\tr.reserve(rt)\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tremaining := edge.capacity - amt\n\tif remaining > 0 {\n\t\testimate += remaining * 3 / 4\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 35 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\t\tr.routePenalty[key] *= 0.25\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 2\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tr.recordFailure(key, routeAmount(rt, failIndex))\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 2 {\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif retry > 0 && retry < r.baseShard {\n\t\t\tr.baseShard = retry\n\t\t}\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 2,
|
|
"parent": 1,
|
|
"score": 0.7835,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.70\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 250_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 50_000_000:\n\t\tparts = 2\n\t}\n\tif spec.MaxParts != 0 && parts > spec.MaxParts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\n\t)\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampAmount(amt, capacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amt < 0 {\n\t\treturn 0\n\t}\n\tif amt > capacity {\n\t\treturn capacity\n\t}\n\treturn amt\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[edge.key.chanID]\n\n\tspent := r.localSpent[edge.key.chanID]\n\tif spent >= available {\n\t\treturn 0\n\t}\n\tavailable -= spent\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.50*prior + 0.50*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif required[item.node] != item.arriving {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tif sending > r.localAvailable(edge) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tif fee < 0 ||\n\t\t\t\t\tamtOver > lnwire.MilliSatoshi(\n\t\t\t\t\t\tmath.MaxInt64,\n\t\t\t\t\t)-fee {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tfee := forwardingEdge.fee(amtOver[i+1])\n\t\tif fee < 0 ||\n\t\t\tamtOver[i+1] > lnwire.MilliSatoshi(\n\t\t\t\tmath.MaxInt64,\n\t\t\t)-fee {\n\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeProbability(rt *route.Route) float64 {\n\tprobability := 1.0\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability *= r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t}\n\n\tif probability < math.SmallestNonzeroFloat64 {\n\t\treturn math.SmallestNonzeroFloat64\n\t}\n\treturn probability\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendCandidate(values []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn values\n\t}\n\n\tseen[value] = true\n\treturn append(values, value)\n}\n\nfunc (r *candidateRouter) shardCandidates(amt,\n\tminimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 12)\n\n\tfor _, numerator := range []int64{\n\t\t100, 84, 70, 58, 48, 40, 33, 27,\n\t} {\n\t\tvalue := lnwire.MilliSatoshi(\n\t\t\tint64(amt) * numerator / 100,\n\t\t)\n\t\tvalues = appendCandidate(\n\t\t\tvalues, seen, value, minimum, amt,\n\t\t)\n\t}\n\n\tvalues = appendCandidate(\n\t\tvalues, seen, r.baseShard, minimum, amt,\n\t)\n\tvalues = appendCandidate(\n\t\tvalues, seen, minimum, minimum, amt,\n\t)\n\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 ||\n\t\tinFlightHtlcs >= r.spec.MaxParts {\n\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tminimum := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestUtility = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range r.shardCandidates(amt, minimum) {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := r.routeProbability(rt)\n\t\tsizeRatio := float64(shard) / float64(minimum)\n\t\tfee := rt.TotalAmount - deliveredAmount(rt)\n\n\t\tutility := math.Log(probability) +\n\t\t\t1.35*math.Log(sizeRatio) -\n\t\t\tfloat64(fee)/2_000_000\n\n\t\tif shard == amt {\n\t\t\tutility += 0.08\n\t\t}\n\t\tif utility > bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief.lowerOK = clampAmount(\n\t\tbelief.lowerOK, edge.capacity,\n\t)\n\tbelief.upperBad = clampAmount(\n\t\tbelief.upperBad, edge.capacity,\n\t)\n\tbelief.estimate = clampAmount(\n\t\tbelief.estimate, edge.capacity,\n\t)\n\n\tif belief.upperBad != 0 &&\n\t\tbelief.lowerOK >= belief.upperBad {\n\n\t\tbelief.upperBad = 0\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordProbeSuccess(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tif edge.capacity > amt {\n\t\testimate += (edge.capacity - amt) * 3 / 4\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc subtractBound(value,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value == 0 || value <= amt {\n\t\treturn 0\n\t}\n\treturn value - amt\n}\n\nfunc addBound(value, amt,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value > capacity-amt {\n\t\treturn capacity\n\t}\n\treturn value + amt\n}\n\nfunc (r *candidateRouter) recordSettlement(\n\tkey candidateEdgeKey, amt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\tif inferred > preEstimate {\n\t\tpreEstimate = inferred\n\t}\n\n\tbelief.lowerOK = subtractBound(belief.lowerOK, amt)\n\tbelief.upperBad = subtractBound(belief.upperBad, amt)\n\tbelief.estimate = subtractBound(preEstimate, amt)\n\tr.saveBelief(key, belief)\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edgeByKey[reverseKey]\n\tif reverseEdge == nil || reverseKey.from == r.source {\n\t\treturn\n\t}\n\n\treverse := r.beliefs[reverseKey]\n\treverse.lowerOK = addBound(\n\t\treverse.lowerOK, amt, reverseEdge.capacity,\n\t)\n\tif reverse.lowerOK < amt {\n\t\treverse.lowerOK = amt\n\t}\n\treverse.estimate = addBound(\n\t\treverse.estimate, amt, reverseEdge.capacity,\n\t)\n\tif reverse.estimate < reverse.lowerOK {\n\t\treverse.estimate = reverse.lowerOK\n\t}\n\tif reverse.upperBad != 0 {\n\t\treverse.upperBad = addBound(\n\t\t\treverse.upperBad, amt, reverseEdge.capacity,\n\t\t)\n\t}\n\n\tr.saveBelief(reverseKey, reverse)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 2\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordProbeSuccess(\n\t\t\t\tkey, routeAmount(rt, i),\n\t\t\t)\n\t\t\tr.routePenalty[key] *= 0.75\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tr.recordFailure(\n\t\t\tkey, routeAmount(rt, failIndex),\n\t\t)\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tretry := lnwire.MilliSatoshi(\n\t\tfloat64(delivered) * lowerRetryFactor,\n\t)\n\tif retry > 0 && retry < r.baseShard {\n\t\tr.baseShard = retry\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 3,
|
|
"parent": 1,
|
|
"score": 0.5285,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 260000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tdiversityCost = 6000000.0\n\tminimumShard = lnwire.MilliSatoshi(10_000)\n\tmaxPlanCorridors = 8\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype pathCandidate struct {\n\tedges []*candidateEdge\n\tlimit lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n\tretryHint lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amount >= value {\n\t\treturn 0\n\t}\n\treturn value - amount\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\tavailable = subtractFloor(available, r.localSpent[key.chanID])\n\tavailable = subtractFloor(available, r.reserved[key])\n\n\treturn available\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.52*prior + 0.48*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]int) ([]*candidateEdge, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 && amtOver >= belief.upperBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tcapacityBonus := 0.0\n\t\t\tif edge.capacity > 0 {\n\t\t\t\tspare := float64(edge.capacity-amtOver) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tcapacityBonus = math.Min(spare, 1) * 18000\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 250 -\n\t\t\t\tcapacityBonus\n\n\t\t\tif count := diversity[edge.key]; count != 0 {\n\t\t\t\tscore += float64(count) * diversityCost\n\t\t\t}\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, 0, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, 0, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 0, errors.New(\"empty route\")\n\t}\n\n\treturn path, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.usable(amtOver[i]) {\n\t\t\treturn nil, errors.New(\"path cannot carry amount\")\n\t\t}\n\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeFeasible(rt *route.Route) bool {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok || r.blocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tamt := routeAmount(rt, i)\n\t\tif edge == nil || !edge.usable(amt) {\n\t\t\treturn false\n\t\t}\n\n\t\tif edge.key.from == r.source {\n\t\t\tif amt > r.localAvailable(key) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\tif reserved >= edge.capacity ||\n\t\t\tamt > edge.capacity-reserved {\n\n\t\t\treturn false\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) pathLimit(path []*candidateEdge,\n\tmaxAmount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif maxAmount <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := maxAmount\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\trt, err := r.buildRoute(mid, path)\n\t\tif err == nil && r.routeFeasible(rt) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc pathID(path []*candidateEdge) string {\n\tid := \"\"\n\tfor _, edge := range path {\n\t\tid += fmt.Sprintf(\n\t\t\t\"%d:%x:%x/\", edge.key.chanID, edge.key.from,\n\t\t\tedge.key.to,\n\t\t)\n\t}\n\treturn id\n}\n\nfunc (r *candidateRouter) probeAmount(remaining lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\n\tprobe := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(parts)*6,\n\t)\n\tif probe < 100_000 {\n\t\tprobe = 100_000\n\t}\n\tif probe > 50_000_000 {\n\t\tprobe = 50_000_000\n\t}\n\tif probe > remaining {\n\t\tprobe = remaining\n\t}\n\n\treturn probe\n}\n\nfunc (r *candidateRouter) planPaths(remaining lnwire.MilliSatoshi,\n\tparts uint32) []pathCandidate {\n\n\tcount := int(parts)\n\tif count < 1 {\n\t\tcount = 1\n\t}\n\tif count > maxPlanCorridors {\n\t\tcount = maxPlanCorridors\n\t}\n\n\tprobes := []lnwire.MilliSatoshi{\n\t\tr.probeAmount(remaining, parts),\n\t}\n\tif probes[0] > minimumShard {\n\t\tprobes = append(probes, minimumShard)\n\t}\n\n\tdiversity := make(map[candidateEdgeKey]int)\n\tseen := make(map[string]bool)\n\tplans := make([]pathCandidate, 0, count)\n\n\tfor len(plans) < count {\n\t\tvar (\n\t\t\tpath []*candidateEdge\n\t\t\tscore float64\n\t\t\terr error\n\t\t)\n\n\t\tfor _, probe := range probes {\n\t\t\tpath, score, err = r.findPath(probe, diversity)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tid := pathID(path)\n\t\tfor _, edge := range path {\n\t\t\tdiversity[edge.key]++\n\t\t}\n\n\t\tif seen[id] {\n\t\t\tif len(plans) != 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tseen[id] = true\n\n\t\tlimit := r.pathLimit(path, remaining)\n\t\tif limit < minimumShard && limit < remaining {\n\t\t\tcontinue\n\t\t}\n\n\t\tplans = append(plans, pathCandidate{\n\t\t\tedges: path,\n\t\t\tlimit: limit,\n\t\t\tscore: score,\n\t\t})\n\t}\n\n\tsort.SliceStable(plans, func(i, j int) bool {\n\t\tpi := r.pathReliability(plans[i])\n\t\tpj := r.pathReliability(plans[j])\n\t\tif math.Abs(pi-pj) > 0.03 {\n\t\t\treturn pi > pj\n\t\t}\n\t\tif plans[i].limit != plans[j].limit {\n\t\t\treturn plans[i].limit > plans[j].limit\n\t\t}\n\t\treturn plans[i].score < plans[j].score\n\t})\n\n\treturn plans\n}\n\nfunc (r *candidateRouter) pathReliability(\n\tplan pathCandidate) float64 {\n\n\tif plan.limit <= 0 {\n\t\treturn 0\n\t}\n\n\tprobe := plan.limit\n\tif probe > 50_000_000 {\n\t\tprobe = 50_000_000\n\t}\n\n\tprobability := 1.0\n\tfor _, edge := range plan.edges {\n\t\tprobability *= r.probability(edge, probe)\n\t}\n\n\treturn probability\n}\n\nfunc plannedAllocation(remaining lnwire.MilliSatoshi,\n\tplans []pathCandidate) lnwire.MilliSatoshi {\n\n\tif len(plans) == 0 {\n\t\treturn 0\n\t}\n\tif len(plans) == 1 {\n\t\tif plans[0].limit < remaining {\n\t\t\treturn plans[0].limit\n\t\t}\n\t\treturn remaining\n\t}\n\n\ttotal := lnwire.MilliSatoshi(0)\n\tother := lnwire.MilliSatoshi(0)\n\tfor i, plan := range plans {\n\t\ttotal += plan.limit\n\t\tif i != 0 {\n\t\t\tother += plan.limit\n\t\t}\n\t}\n\n\tif total <= 0 {\n\t\treturn 0\n\t}\n\n\tallocation := lnwire.MilliSatoshi(\n\t\tfloat64(remaining) *\n\t\t\tfloat64(plans[0].limit) / float64(total),\n\t)\n\n\trequiredNow := subtractFloor(remaining, other)\n\tif allocation < requiredNow {\n\t\tallocation = requiredNow\n\t}\n\n\tfairMinimum := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(len(plans)),\n\t)\n\tif allocation < fairMinimum &&\n\t\tplans[0].limit >= fairMinimum {\n\n\t\tallocation = fairMinimum\n\t}\n\n\tif allocation > plans[0].limit {\n\t\tallocation = plans[0].limit\n\t}\n\tif allocation > remaining {\n\t\tallocation = remaining\n\t}\n\tif allocation < minimumShard && remaining >= minimumShard {\n\t\tif plans[0].limit >= minimumShard {\n\t\t\tallocation = minimumShard\n\t\t}\n\t}\n\n\treturn allocation\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tr.reserved[key] += routeAmount(rt, i)\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tplans := r.planPaths(amt, partsLeft)\n\tif len(plans) != 0 {\n\t\tshard := plannedAllocation(amt, plans)\n\n\t\tif r.retryHint != 0 && shard > r.retryHint {\n\t\t\tshard = r.retryHint\n\t\t}\n\t\tif shard > plans[0].limit {\n\t\t\tshard = plans[0].limit\n\t\t}\n\t\tif shard > amt {\n\t\t\tshard = amt\n\t\t}\n\n\t\tfor shard > 0 {\n\t\t\trt, err := r.buildRoute(shard, plans[0].edges)\n\t\t\tif err == nil && r.routeFeasible(rt) {\n\t\t\t\tr.reserve(rt)\n\t\t\t\treturn rt, nil\n\t\t\t}\n\n\t\t\tnext := shard * 2 / 3\n\t\t\tif next >= shard {\n\t\t\t\tnext = shard - 1\n\t\t\t}\n\t\t\tif next < minimumShard && amt >= minimumShard {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tshard = next\n\t\t}\n\t}\n\n\tcandidates := []lnwire.MilliSatoshi{\n\t\tamt,\n\t\tceilDiv(amt, lnwire.MilliSatoshi(partsLeft)),\n\t\tamt * 2 / 3,\n\t\tamt / 2,\n\t\tamt / 3,\n\t\tminimumShard,\n\t}\n\tif r.retryHint != 0 {\n\t\tcandidates = append(\n\t\t\t[]lnwire.MilliSatoshi{r.retryHint}, candidates...,\n\t\t)\n\t}\n\n\ttried := make(map[lnwire.MilliSatoshi]bool)\n\tvar lastErr error\n\tfor _, shard := range candidates {\n\t\tif shard <= 0 || shard > amt || tried[shard] {\n\t\t\tcontinue\n\t\t}\n\t\ttried[shard] = true\n\n\t\tpath, _, err := r.findPath(\n\t\t\tshard, make(map[candidateEdgeKey]int),\n\t\t)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\trt, err := r.buildRoute(shard, path)\n\t\tif err != nil || !r.routeFeasible(rt) {\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tr.reserve(rt)\n\t\treturn rt, nil\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tinferred := amt\n\tremaining := edge.capacity - amt\n\tif remaining > 0 {\n\t\tinferred += remaining * 3 / 4\n\t}\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\tif inferred > preEstimate {\n\t\tpreEstimate = inferred\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt)\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tif belief.upperBad != 0 {\n\t\tbelief.upperBad = subtractFloor(belief.upperBad, amt)\n\t\tif belief.upperBad == 0 {\n\t\t\tbelief.upperBad = 1\n\t\t}\n\t}\n\n\tlikelyRemaining := lnwire.MilliSatoshi(0)\n\tif edge.capacity > amt {\n\t\tlikelyRemaining = (edge.capacity - amt) * 2 / 3\n\t}\n\tif belief.estimate < likelyRemaining {\n\t\tbelief.estimate = likelyRemaining\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 30 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\toutgoing := i + 1\n\t\tif outgoing < len(rt.Hops) {\n\t\t\treturn outgoing\n\t\t}\n\t\treturn -1\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.45\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > minimumShard {\n\t\t\tr.retryHint = delivered * 2 / 3\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.75\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(key, routeAmount(rt, failIndex))\n\t\tr.routePenalty[key] += riskCostMsat * 1.4\n\n\tdefault:\n\t\tr.routePenalty[key] += riskCostMsat * 0.75\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered > minimumShard {\n\t\tfactor := lnwire.MilliSatoshi(2)\n\t\tdivisor := lnwire.MilliSatoshi(3)\n\t\tif r.consecutiveFailures >= 3 {\n\t\t\tfactor = 1\n\t\t\tdivisor = 2\n\t\t}\n\n\t\tretry := delivered * factor / divisor\n\t\tif retry < minimumShard {\n\t\t\tretry = minimumShard\n\t\t}\n\t\tr.retryHint = retry\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 4,
|
|
"parent": 1,
|
|
"score": -0.15,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 250000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tlowerRetryFactor = 0.68\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tretryCap lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc minMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.025)\n\thighMode := 0.48 / (1 + math.Exp(14*(x-0.78)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[edge.key.chanID]\n\tspent := r.localSpent[edge.key.chanID]\n\tif spent >= available {\n\t\treturn 0\n\t}\n\tavailable -= spent\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.07, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tweight := 0.48\n\tif belief.lowerOK != 0 || belief.upperBad != 0 {\n\t\tweight = 0.64\n\t}\n\n\treturn clampProbability((1-weight)*prior + weight*point)\n}\n\nfunc (r *candidateRouter) edgeSafeLimit(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tif r.blocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tlimit := edge.capacity\n\tif edge.maxHTLC != 0 && edge.maxHTLC < limit {\n\t\tlimit = edge.maxHTLC\n\t}\n\n\tif edge.key.from == r.source {\n\t\tlimit = minMSat(limit, r.localAvailable(edge))\n\t\tif limit < edge.minHTLC {\n\t\t\treturn 0\n\t\t}\n\t\treturn limit\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= limit {\n\t\treturn 0\n\t}\n\tlimit -= reserved\n\n\tbelief := r.beliefs[edge.key]\n\tsafe := limit * 72 / 100\n\n\tif belief.estimate != 0 {\n\t\tsafe = belief.estimate * 90 / 100\n\t}\n\tif belief.upperBad != 0 {\n\t\tretry := belief.upperBad * 68 / 100\n\t\tsafe = maxMSat(safe, retry)\n\t}\n\tif belief.lowerOK != 0 {\n\t\tsafe = maxMSat(safe, belief.lowerOK)\n\t}\n\n\tif belief.upperBad != 0 && safe >= belief.upperBad {\n\t\tsafe = belief.upperBad - 1\n\t}\n\tif safe > limit {\n\t\tsafe = limit\n\t}\n\tif safe < edge.minHTLC {\n\t\treturn 0\n\t}\n\n\treturn safe\n}\n\ntype widestItem struct {\n\tnode route.Vertex\n\twidth lnwire.MilliSatoshi\n}\n\ntype widestQueue []*widestItem\n\nfunc (q widestQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q widestQueue) Less(i, j int) bool {\n\treturn q[i].width > q[j].width\n}\n\nfunc (q widestQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *widestQueue) Push(value any) {\n\t*q = append(*q, value.(*widestItem))\n}\n\nfunc (q *widestQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) widestAmount(\n\tmaximum lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif maximum <= 0 {\n\t\treturn 0\n\t}\n\n\twidth := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: maximum,\n\t}\n\tpq := &widestQueue{}\n\theap.Push(pq, &widestItem{\n\t\tnode: r.spec.Target,\n\t\twidth: maximum,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*widestItem)\n\t\tif item.width != width[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\treturn item.width\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tlimit := r.edgeSafeLimit(edge)\n\t\t\tif limit == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcandidate := minMSat(item.width, limit)\n\t\t\tif candidate <= width[edge.key.from] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\twidth[edge.key.from] = candidate\n\t\t\theap.Push(pq, &widestItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\twidth: candidate,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn 0\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tif reserved >= edge.capacity ||\n\t\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t} else if sending > r.localAvailable(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\treturn key, ok\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.retryCap = 0\n\t}\n\tr.lastRemaining = amt\n\n\tfor key, penalty := range r.routePenalty {\n\t\tpenalty *= 0.90\n\t\tif penalty < 1 {\n\t\t\tdelete(r.routePenalty, key)\n\t\t} else {\n\t\t\tr.routePenalty[key] = penalty\n\t\t}\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\tpartsLeft = 1\n\t}\n\n\tdesired := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\tif partsLeft == 1 {\n\t\tdesired = amt\n\t}\n\n\twidest := r.widestAmount(amt)\n\tshard := desired\n\tif widest != 0 {\n\t\tsafe := widest * 92 / 100\n\t\tif safe < minShardMsat {\n\t\t\tsafe = minMSat(widest, minShardMsat)\n\t\t}\n\t\tshard = minMSat(shard, safe)\n\t}\n\n\tif r.retryCap != 0 && shard > r.retryCap {\n\t\tshard = r.retryCap\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tminShard := minShardMsat\n\tif amt < minShard {\n\t\tminShard = amt\n\t}\n\tif shard < minShard {\n\t\tshard = minShard\n\t}\n\n\tvar lastErr error\n\ttried := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor shard >= minShard {\n\t\tif !tried[shard] {\n\t\t\ttried[shard] = true\n\t\t\trt, err := r.findRoute(shard)\n\t\t\tif err == nil {\n\t\t\t\tr.reserve(rt)\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t\tlastErr = err\n\t\t}\n\n\t\tif shard == minShard {\n\t\t\tbreak\n\t\t}\n\n\t\tnextShard := lnwire.MilliSatoshi(\n\t\t\tfloat64(shard) * lowerRetryFactor,\n\t\t)\n\t\tif nextShard >= shard {\n\t\t\tnextShard = shard - 1\n\t\t}\n\t\tif nextShard < minShard {\n\t\t\tnextShard = minShard\n\t\t}\n\t\tshard = nextShard\n\t}\n\n\tif !tried[amt] && partsLeft == 1 {\n\t\trt, err := r.findRoute(amt)\n\t\tif err == nil {\n\t\t\tr.reserve(rt)\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tif edge.capacity > amt {\n\t\testimate += (edge.capacity - amt) * 3 / 4\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\n\tlower := maxMSat(belief.lowerOK, amt)\n\tif lower > amt {\n\t\tbelief.lowerOK = lower - amt\n\t} else {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 3 / 4\n\t}\n\testimate := maxMSat(belief.estimate, inferred)\n\tif estimate > amt {\n\t\tbelief.estimate = estimate - amt\n\t} else {\n\t\tbelief.estimate = 0\n\t}\n\n\tif belief.upperBad != 0 {\n\t\tif belief.upperBad > amt {\n\t\t\tbelief.upperBad -= amt\n\t\t} else {\n\t\t\tbelief.upperBad = 0\n\t\t}\n\t}\n\n\tif belief.estimate > edge.capacity {\n\t\tbelief.estimate = edge.capacity\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 35 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\t\tr.consecutiveFailures = 0\n\t\tr.retryCap = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 3\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := delivered * 3 / 4\n\t\tif retry > 0 &&\n\t\t\t(r.retryCap == 0 || retry < r.retryCap) {\n\n\t\t\tr.retryCap = retry\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.50\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmt := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmt)\n\t\tr.routePenalty[key] += riskCostMsat\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif retry > 0 &&\n\t\t\t(r.retryCap == 0 || retry < r.retryCap) {\n\n\t\t\tr.retryCap = retry\n\t\t}\n\t}\n\n\tr.consecutiveFailures++\n\tif r.consecutiveFailures >= 3 {\n\t\tfor key, penalty := range r.routePenalty {\n\t\t\tr.routePenalty[key] = penalty * 0.72\n\t\t}\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 5,
|
|
"parent": 2,
|
|
"score": 0.0,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\triskCostMsat = 320000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\tdiversityCost = 2200000.0\n\tminimumShard = lnwire.MilliSatoshi(10_000)\n\tmaxPlanCorridors = 8\n\tmaxPathHops = 24\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype pathCandidate struct {\n\tedges []*candidateEdge\n\thardLimit lnwire.MilliSatoshi\n\tlikelyLimit lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n\tretryHint lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, ok := r.edgeByKey[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\n\treturn (a + b - 1) / b\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif amount >= value {\n\t\treturn 0\n\t}\n\n\treturn value - amount\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < minProbability {\n\t\treturn minProbability\n\t}\n\tif p > maxProbability {\n\t\treturn maxProbability\n\t}\n\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.026)\n\thighMode := 0.475 / (1 + math.Exp(15*(x-0.79)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tkey candidateEdgeKey) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[key.chanID]\n\tavailable = subtractFloor(available, r.localSpent[key.chanID])\n\tavailable = subtractFloor(available, r.reserved[key])\n\n\treturn available\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge.key) {\n\t\t\treturn minProbability\n\t\t}\n\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.055, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.43*prior + 0.57*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n\thops int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\tif q[i].score == q[j].score {\n\t\treturn q[i].hops < q[j].hops\n\t}\n\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\n\treturn item\n}\n\nfunc (r *candidateRouter) edgeCost(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]int) float64 {\n\n\tp := r.probability(edge, amt)\n\tcost := float64(fee) - math.Log(p)*riskCostMsat + 500\n\n\tif edge.capacity > 0 {\n\t\tload := float64(amt) / float64(edge.capacity)\n\t\tcost += 22000 * load * load\n\t}\n\n\tcost += r.routePenalty[edge.key]\n\tif count := diversity[edge.key]; count != 0 {\n\t\tcost += float64(count) * diversityCost\n\t}\n\n\tif cost < 1 {\n\t\treturn 1\n\t}\n\n\treturn cost\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]int) ([]*candidateEdge, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\thopCount := map[route.Vertex]int{\n\t\tr.spec.Target: 0,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tsettled := make(map[route.Vertex]bool)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score != bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif settled[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tsettled[item.node] = true\n\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxPathHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.spec.Target {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif settled[edge.key.from] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge.key) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 && amtOver >= belief.upperBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tsending := amtOver\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tscore := item.score + r.edgeCost(\n\t\t\t\tedge, amtOver, fee, diversity,\n\t\t\t)\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\thopCount[edge.key.from] = item.hops + 1\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t\thops: item.hops + 1,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvisited := make(map[route.Vertex]bool)\n\tpath := make([]*candidateEdge, 0, hopCount[r.source])\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, 0, errors.New(\"route contains a cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, 0, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > maxPathHops {\n\t\t\treturn nil, 0, errors.New(\"route exceeds hop limit\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 0, errors.New(\"empty route\")\n\t}\n\n\treturn path, score, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.usable(amtOver[i]) {\n\t\t\treturn nil, errors.New(\"path cannot carry amount\")\n\t\t}\n\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, ok := r.edgeByKey[key]\n\n\treturn key, ok\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeFeasible(rt *route.Route) bool {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok || r.blocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tamt := routeAmount(rt, i)\n\t\tif edge == nil || !edge.usable(amt) {\n\t\t\treturn false\n\t\t}\n\n\t\tif edge.key.from == r.source {\n\t\t\tif amt > r.localAvailable(key) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\tif reserved >= edge.capacity ||\n\t\t\tamt > edge.capacity-reserved {\n\n\t\t\treturn false\n\t\t}\n\n\t\tbelief := r.beliefs[key]\n\t\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) credibleAmount(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tif edge.key.from == r.source {\n\t\treturn r.localAvailable(edge.key)\n\t}\n\n\tlimit := edge.capacity\n\tif edge.maxHTLC != 0 && edge.maxHTLC < limit {\n\t\tlimit = edge.maxHTLC\n\t}\n\n\treserved := r.reserved[edge.key]\n\tlimit = subtractFloor(limit, reserved)\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && belief.upperBad-1 < limit {\n\t\tlimit = belief.upperBad - 1\n\t}\n\n\tif belief.estimate != 0 {\n\t\tlikely := belief.estimate * 92 / 100\n\t\tif belief.lowerOK > likely {\n\t\t\tlikely = belief.lowerOK\n\t\t}\n\t\tif likely < limit {\n\t\t\tlimit = likely\n\t\t}\n\n\t\treturn limit\n\t}\n\n\tpriorLimit := edge.capacity * 72 / 100\n\tpriorLimit = subtractFloor(priorLimit, reserved)\n\tif priorLimit < limit {\n\t\tlimit = priorLimit\n\t}\n\n\treturn limit\n}\n\nfunc (r *candidateRouter) routeLikely(rt *route.Route) bool {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tif edge == nil || routeAmount(rt, i) > r.credibleAmount(edge) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) amountLimit(path []*candidateEdge,\n\tmaxAmount lnwire.MilliSatoshi, likely bool) lnwire.MilliSatoshi {\n\n\tif maxAmount <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := maxAmount\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\trt, err := r.buildRoute(mid, path)\n\t\tok := err == nil && r.routeFeasible(rt)\n\t\tif ok && likely {\n\t\t\tok = r.routeLikely(rt)\n\t\t}\n\n\t\tif ok {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc pathID(path []*candidateEdge) string {\n\tid := \"\"\n\tfor _, edge := range path {\n\t\tid += fmt.Sprintf(\n\t\t\t\"%d:%x:%x/\", edge.key.chanID, edge.key.from,\n\t\t\tedge.key.to,\n\t\t)\n\t}\n\n\treturn id\n}\n\nfunc (r *candidateRouter) pathProbability(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\tprobability := 1.0\n\tfor i, edge := range path {\n\t\tprobability *= r.probability(edge, routeAmount(rt, i))\n\t}\n\n\treturn probability\n}\n\nfunc (r *candidateRouter) discoveryProbe(remaining\n\tlnwire.MilliSatoshi, parts uint32) lnwire.MilliSatoshi {\n\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\n\tprobe := ceilDiv(\n\t\tremaining, lnwire.MilliSatoshi(parts)*8,\n\t)\n\tif probe < 100_000 {\n\t\tprobe = 100_000\n\t}\n\tif probe > 25_000_000 {\n\t\tprobe = 25_000_000\n\t}\n\tif probe > remaining {\n\t\tprobe = remaining\n\t}\n\n\treturn probe\n}\n\nfunc (r *candidateRouter) planPaths(remaining lnwire.MilliSatoshi,\n\tparts uint32) []pathCandidate {\n\n\tcount := int(parts)\n\tif count < 1 {\n\t\tcount = 1\n\t}\n\tif count > maxPlanCorridors {\n\t\tcount = maxPlanCorridors\n\t}\n\n\tprobes := []lnwire.MilliSatoshi{\n\t\tr.discoveryProbe(remaining, parts),\n\t\t100_000,\n\t\tminimumShard,\n\t}\n\n\tdiversity := make(map[candidateEdgeKey]int)\n\tseen := make(map[string]bool)\n\tplans := make([]pathCandidate, 0, count)\n\tmaxSearches := count * 5\n\n\tfor search := 0; search < maxSearches &&\n\t\tlen(plans) < count; search++ {\n\n\t\tvar (\n\t\t\tpath []*candidateEdge\n\t\t\tscore float64\n\t\t\terr error\n\t\t)\n\n\t\tfor _, probe := range probes {\n\t\t\tif probe <= 0 || probe > remaining {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath, score, err = r.findPath(probe, diversity)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tid := pathID(path)\n\t\tfor _, edge := range path {\n\t\t\tdiversity[edge.key]++\n\t\t}\n\t\tif seen[id] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[id] = true\n\n\t\thardLimit := r.amountLimit(path, remaining, false)\n\t\tif hardLimit <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tlikelyLimit := r.amountLimit(path, hardLimit, true)\n\t\tif likelyLimit < minimumShard {\n\t\t\tlikelyLimit = hardLimit\n\t\t\tif likelyLimit > 1_000_000 {\n\t\t\t\tlikelyLimit = 1_000_000\n\t\t\t}\n\t\t}\n\n\t\tplans = append(plans, pathCandidate{\n\t\t\tedges: path,\n\t\t\thardLimit: hardLimit,\n\t\t\tlikelyLimit: likelyLimit,\n\t\t\tscore: score,\n\t\t})\n\t}\n\n\tsort.SliceStable(plans, func(i, j int) bool {\n\t\tpi := r.pathProbability(\n\t\t\tplans[i].edges, plans[i].likelyLimit,\n\t\t)\n\t\tpj := r.pathProbability(\n\t\t\tplans[j].edges, plans[j].likelyLimit,\n\t\t)\n\t\tif math.Abs(pi-pj) > 0.04 {\n\t\t\treturn pi > pj\n\t\t}\n\t\tif plans[i].likelyLimit != plans[j].likelyLimit {\n\t\t\treturn plans[i].likelyLimit > plans[j].likelyLimit\n\t\t}\n\n\t\treturn plans[i].score < plans[j].score\n\t})\n\n\treturn plans\n}\n\nfunc sumPlanLimits(plans []pathCandidate, start, count int,\n\tlikely bool) lnwire.MilliSatoshi {\n\n\ttotal := lnwire.MilliSatoshi(0)\n\tend := start + count\n\tif end > len(plans) {\n\t\tend = len(plans)\n\t}\n\n\tfor i := start; i < end; i++ {\n\t\tif likely {\n\t\t\ttotal += plans[i].likelyLimit\n\t\t} else {\n\t\t\ttotal += plans[i].hardLimit\n\t\t}\n\t}\n\n\treturn total\n}\n\nfunc plannedAllocation(remaining lnwire.MilliSatoshi,\n\tparts uint32, plans []pathCandidate) lnwire.MilliSatoshi {\n\n\tif len(plans) == 0 || parts == 0 {\n\t\treturn 0\n\t}\n\n\tusable := len(plans)\n\tif usable > int(parts) {\n\t\tusable = int(parts)\n\t}\n\n\tfirstLimit := plans[0].likelyLimit\n\ttotalLikely := sumPlanLimits(plans, 0, usable, true)\n\tif totalLikely < remaining {\n\t\tfirstLimit = plans[0].hardLimit\n\t}\n\n\totherCapacity := sumPlanLimits(plans, 1, usable-1, true)\n\tif totalLikely < remaining {\n\t\totherCapacity = sumPlanLimits(\n\t\t\tplans, 1, usable-1, false,\n\t\t)\n\t}\n\n\trequiredNow := subtractFloor(remaining, otherCapacity)\n\tallocation := requiredNow\n\n\ttotalWeight := totalLikely\n\tif totalWeight < remaining {\n\t\ttotalWeight = sumPlanLimits(plans, 0, usable, false)\n\t}\n\tif totalWeight > 0 {\n\t\tweighted := lnwire.MilliSatoshi(\n\t\t\tfloat64(remaining) *\n\t\t\t\tfloat64(firstLimit) / float64(totalWeight),\n\t\t)\n\t\tif weighted > allocation {\n\t\t\tallocation = weighted\n\t\t}\n\t}\n\n\tif parts == 1 {\n\t\tallocation = remaining\n\t}\n\tif allocation > firstLimit {\n\t\tallocation = firstLimit\n\t}\n\tif allocation > plans[0].hardLimit {\n\t\tallocation = plans[0].hardLimit\n\t}\n\tif allocation > remaining {\n\t\tallocation = remaining\n\t}\n\tif allocation < minimumShard && remaining >= minimumShard &&\n\t\tplans[0].hardLimit >= minimumShard {\n\n\t\tallocation = minimumShard\n\t}\n\n\treturn allocation\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) tryRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !r.routeFeasible(rt) {\n\t\treturn nil, errors.New(\"route is not feasible\")\n\t}\n\n\tr.reserve(rt)\n\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 {\n\t\treturn nil, errors.New(\"payment permits no parts\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tplans := r.planPaths(amt, partsLeft)\n\n\tif len(plans) != 0 {\n\t\tshard := plannedAllocation(amt, partsLeft, plans)\n\t\tif r.retryHint != 0 && shard > r.retryHint {\n\t\t\tshard = r.retryHint\n\t\t}\n\t\tif partsLeft == 1 {\n\t\t\tshard = amt\n\t\t}\n\n\t\tfor _, plan := range plans {\n\t\t\ttryAmt := shard\n\t\t\tif tryAmt > plan.hardLimit {\n\t\t\t\ttryAmt = plan.hardLimit\n\t\t\t}\n\t\t\tif tryAmt <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trt, err := r.tryRoute(tryAmt, plan.edges)\n\t\t\tif err == nil {\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t}\n\t}\n\n\tcandidates := []lnwire.MilliSatoshi{\n\t\tamt,\n\t\tceilDiv(amt, lnwire.MilliSatoshi(partsLeft)),\n\t\tamt * 3 / 4,\n\t\tamt * 2 / 3,\n\t\tamt / 2,\n\t\tamt / 3,\n\t\tamt / 4,\n\t\tminimumShard,\n\t}\n\tif r.retryHint != 0 {\n\t\tcandidates = append(\n\t\t\t[]lnwire.MilliSatoshi{r.retryHint}, candidates...,\n\t\t)\n\t}\n\n\ttried := make(map[lnwire.MilliSatoshi]bool)\n\tvar lastErr error\n\tfor _, shard := range candidates {\n\t\tif shard <= 0 || shard > amt || tried[shard] {\n\t\t\tcontinue\n\t\t}\n\t\tif partsLeft == 1 && shard != amt {\n\t\t\tcontinue\n\t\t}\n\t\ttried[shard] = true\n\n\t\tpath, _, err := r.findPath(\n\t\t\tshard, make(map[candidateEdgeKey]int),\n\t\t)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\trt, err := r.tryRoute(shard, path)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 4 / 5\n\t}\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\n\tinferred := amt\n\tif edge.capacity > amt {\n\t\tinferred += (edge.capacity - amt) * 4 / 5\n\t}\n\tif inferred > preEstimate {\n\t\tpreEstimate = inferred\n\t}\n\n\tbelief.estimate = subtractFloor(preEstimate, amt)\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tif belief.upperBad != 0 {\n\t\tbelief.upperBad = subtractFloor(belief.upperBad, amt)\n\t\tif belief.upperBad == 0 {\n\t\t\tbelief.upperBad = 1\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt / 4\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\toutgoing := i + 1\n\t\tif outgoing < len(rt.Hops) {\n\t\t\treturn outgoing\n\t\t}\n\n\t\treturn -1\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.15\n\t\t}\n\n\t\tr.consecutiveFailures = 0\n\t\tr.retryHint = 0\n\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := r.routeEdge(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.65\n\t\t\t}\n\t\t}\n\n\t\tr.consecutiveFailures++\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > minimumShard {\n\t\t\tr.retryHint = delivered / 2\n\t\t\tif r.retryHint < minimumShard {\n\t\t\t\tr.retryHint = minimumShard\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, ok := r.routeEdge(rt, i)\n\t\tif ok {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.65\n\t\t}\n\t}\n\n\tkey, ok := r.routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.recordFailure(key, routeAmount(rt, failIndex))\n\t\tr.routePenalty[key] += riskCostMsat * 1.8\n\n\tdefault:\n\t\tr.routePenalty[key] += riskCostMsat\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tif delivered > minimumShard {\n\t\tretry := delivered * 3 / 5\n\t\tif r.consecutiveFailures >= 2 {\n\t\t\tretry = delivered / 2\n\t\t}\n\t\tif r.consecutiveFailures >= 4 {\n\t\t\tretry = delivered / 3\n\t\t}\n\t\tif retry < minimumShard {\n\t\t\tretry = minimumShard\n\t\t}\n\t\tr.retryHint = retry\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 6,
|
|
"parent": 0,
|
|
"score": 0.7947,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := sharedBeliefs.values[key]\n\t\tif !ok {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\t// The first term models the small depleted-side tail. The second\n\t// models the large liquid-side mode and its cliff near capacity.\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.10\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\n\tconfidence := math.Min(0.78, 0.22*float64(belief.samples))\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tutility float64\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{r.spec.Target: 0}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: deliver,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: deliver,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(item.amt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := item.amt + r.reserved[edge.key]\n\t\t\tif totalLiquidity > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotalLiquidity >\n\t\t\t\t\tr.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amt)\n\t\t\tedgeFee := edge.fee(item.amt)\n\t\t\tsending := item.amt + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = item.amt\n\t\t\t}\n\n\t\t\t// Reliability dominates. A million-msat risk scale still\n\t\t\t// permits fees to break ties between similarly reliable paths.\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_000_000 +\n\t\t\t\t0.015 +\n\t\t\t\t0.32*float64(r.edgeFailures[edge.key])\n\t\t\tcandidate := item.score + step\n\n\t\t\told, exists := score[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, keys, amounts, nil\n}\n\nfunc addCandidate(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 14)\n\n\taddCandidate(&values, seen, amt, minimum, amt)\n\taddCandidate(&values, seen, amt*3/4, minimum, amt)\n\taddCandidate(&values, seen, amt*2/3, minimum, amt)\n\taddCandidate(&values, seen, amt/2, minimum, amt)\n\taddCandidate(&values, seen, amt/3, minimum, amt)\n\taddCandidate(&values, seen, minimum*2, minimum, amt)\n\taddCandidate(&values, seen, minimum*3/2, minimum, amt)\n\taddCandidate(&values, seen, minimum, minimum, amt)\n\n\tif r.lastFailedAmt > 0 {\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*3/4,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddCandidate(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\t// Failure bounds are useful shard breakpoints. Only retain the largest\n\t// few after deduplication to keep route search bounded.\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] || belief.upperFail <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tbound := belief.upperFail * 3 / 4\n\t\tif bound >= minimum && bound <= amt {\n\t\t\tbounds = append(bounds, bound)\n\t\t}\n\t}\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 4 {\n\t\tbounds = bounds[:4]\n\t}\n\tfor _, bound := range bounds {\n\t\taddCandidate(&values, seen, bound, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= 48 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// This approximates joint route-set planning. It prices route\n\t\t// failure, fees, and the number of shards needed to cover the\n\t\t// remaining amount while reservations steer concurrent shards onto\n\t\t// distinct corridors.\n\t\tshardRatio := float64(amt) / float64(shard)\n\t\tchoice.utility = -math.Log(choice.probability) +\n\t\t\t0.30*math.Log(shardRatio) +\n\t\t\tfloat64(choice.fee)/2_000_000\n\n\t\tif best == nil || choice.utility < best.utility {\n\t\t\tbest = choice\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tfor i, key := range best.keys {\n\t\tr.reserved[key] += best.amounts[i]\n\t}\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\n\n\tkeys := make([]edgeKey, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkeys[i] = edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(\n\tkey edgeKey, belief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 3\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + amt/3) / 3\n\t}\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount lnwire.MilliSatoshi,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\treturn value - amount\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tpreEstimate := belief.estimate\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\toptimistic := edge.capacity * 9 / 10\n\tif preEstimate < optimistic {\n\t\tpreEstimate = (preEstimate + optimistic) / 2\n\t}\n\n\t// Settlement moves liquidity away from this direction, so shift all\n\t// evidence by the amount that just traversed the channel.\n\tbelief.estimate = subtractFloor(preEstimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeEdgeData(rt)\n\tfor i, key := range keys {\n\t\treserved := r.reserved[key]\n\t\tif reserved <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = reserved - amounts[i]\n\t\t}\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, key := range keys {\n\t\tr.edgeFailures[key]++\n\t}\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\treturn nil\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// A route using an advertised policy that is rejected for fees or\n\t\t// timelocks should not be retried during this payment.\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
|
|
},
|
|
"role": "best"
|
|
},
|
|
{
|
|
"id": 7,
|
|
"parent": 3,
|
|
"score": 0.5021,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tmaxRouteAttempts = 48\n\tmaxAmountChoices = 24\n\tmaxForecastSearch = 160\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsamples uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[edgeKey]liquidityBelief\n}{\n\tvalues: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tedgeFailures map[edgeKey]uint32\n\tbroken map[edgeKey]bool\n\n\tlastFailedAmt lnwire.MilliSatoshi\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tedgeFailures: make(map[edgeKey]uint32),\n\t\tbroken: make(map[edgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, exists := r.edges[key]; exists {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, exists := sharedBeliefs.values[key]\n\t\tif !exists {\n\t\t\tbelief.estimate = edge.capacity / 2\n\t\t}\n\t\tr.beliefs[key] = belief\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-18*x)\n\thighMode := 0.495 / (1 + math.Exp(22*(x-0.88)))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total > edge.capacity {\n\t\treturn 0.005\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\n\t\treturn 0.001\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(total, edge.capacity)\n\tif belief.samples == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.10\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(total)-float64(belief.estimate))/scale,\n\t))\n\tconfidence := math.Min(0.80, 0.20*float64(belief.samples))\n\n\treturn clampProbability(\n\t\t(1-confidence)*prior + confidence*point,\n\t)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\n\treturn last\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tdeliver lnwire.MilliSatoshi\n\tprobability float64\n\tfee lnwire.MilliSatoshi\n\tkeys []edgeKey\n\tamounts []lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tscore := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: deliver,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, exists := score[item.node]\n\t\tif !exists || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tneeded := required[item.node]\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.broken[edge.key] || !edge.usable(needed) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := needed + r.reserved[edge.key]\n\t\t\tif total > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotal > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, needed)\n\t\t\tedgeFee := edge.fee(needed)\n\t\t\tsending := needed + edgeFee\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tedgeFee = 0\n\t\t\t\tsending = needed\n\t\t\t}\n\n\t\t\tfailurePenalty := 0.20 *\n\t\t\t\tfloat64(r.edgeFailures[edge.key])\n\t\t\tstep := -math.Log(probability) +\n\t\t\t\tfloat64(edgeFee)/2_000_000 +\n\t\t\t\t0.012 + failurePenalty\n\t\t\tcandidate := item.score + step\n\n\t\t\told, found := score[edge.key.from]\n\t\t\tif found && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = candidate\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, keys, amounts, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tfor i, key := range keys {\n\t\tprobability *= r.edgeProbability(r.edges[key], amounts[i])\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tdeliver: deliver,\n\t\tprobability: probability,\n\t\tfee: rt.TotalAmount - deliver,\n\t\tkeys: keys,\n\t\tamounts: amounts,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, []edgeKey,\n\t[]lnwire.MilliSatoshi, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edges) {\n\t\t\treturn nil, nil, nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, nil, nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tamounts[len(path)-1] = deliver\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\toutgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tkeys := make([]edgeKey, len(path))\n\tfor i, edge := range path {\n\t\tforward := deliver\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t\tkeys[i] = edge.key\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, keys, amounts, nil\n}\n\nfunc addAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc ceilDivide(value lnwire.MilliSatoshi,\n\tdivisor uint32) lnwire.MilliSatoshi {\n\n\td := lnwire.MilliSatoshi(divisor)\n\treturn (value + d - 1) / d\n}\n\nfunc (r *candidateRouter) candidateAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tminimum := ceilDivide(amt, partsLeft)\n\tif minimum < 1_000 {\n\t\tminimum = 1_000\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, maxAmountChoices)\n\n\t// Enumerate balanced and deliberately unequal allocations for every\n\t// feasible part count. Reservations make subsequent calls choose other\n\t// corridors, so these values act as the first shard of a joint plan.\n\tfor parts := uint32(1); parts <= partsLeft; parts++ {\n\t\tbase := ceilDivide(amt, parts)\n\t\taddAmount(&values, seen, base, minimum, amt)\n\n\t\tif parts > 1 {\n\t\t\taddAmount(\n\t\t\t\t&values, seen, base*9/8, minimum, amt,\n\t\t\t)\n\t\t\taddAmount(\n\t\t\t\t&values, seen, base*5/4, minimum, amt,\n\t\t\t)\n\t\t\taddAmount(\n\t\t\t\t&values, seen, base*3/2, minimum, amt,\n\t\t\t)\n\t\t}\n\t}\n\n\tif r.lastFailedAmt > 0 {\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt*4/5,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt*2/3,\n\t\t\tminimum, amt,\n\t\t)\n\t\taddAmount(\n\t\t\t&values, seen, r.lastFailedAmt/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tvar bounds []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tif r.broken[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK >= minimum && belief.lowerOK <= amt {\n\t\t\tbounds = append(bounds, belief.lowerOK)\n\t\t}\n\t\tif belief.upperFail > 1 {\n\t\t\tvalue := belief.upperFail * 4 / 5\n\t\t\tif value >= minimum && value <= amt {\n\t\t\t\tbounds = append(bounds, value)\n\t\t\t}\n\t\t}\n\t\tif belief.estimate >= minimum && belief.estimate <= amt {\n\t\t\tbounds = append(bounds, belief.estimate*9/10)\n\t\t}\n\t}\n\n\tsort.Slice(bounds, func(i, j int) bool {\n\t\treturn bounds[i] > bounds[j]\n\t})\n\tif len(bounds) > 8 {\n\t\tbounds = bounds[:8]\n\t}\n\tfor _, value := range bounds {\n\t\taddAmount(&values, seen, value, minimum, amt)\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\tif len(values) > maxAmountChoices {\n\t\tvalues = values[:maxAmountChoices]\n\t}\n\n\treturn values\n}\n\nfunc reserveChoice(reserved map[edgeKey]lnwire.MilliSatoshi,\n\tchoice *routeChoice) {\n\n\tfor i, key := range choice.keys {\n\t\treserved[key] += choice.amounts[i]\n\t}\n}\n\nfunc restoreReservations(destination,\n\tsnapshot map[edgeKey]lnwire.MilliSatoshi) {\n\n\tfor key := range destination {\n\t\tdelete(destination, key)\n\t}\n\tfor key, value := range snapshot {\n\t\tdestination[key] = value\n\t}\n}\n\nfunc copyReservations(\n\tsource map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[edgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, value := range source {\n\t\tresult[key] = value\n\t}\n\n\treturn result\n}\n\nfunc choiceCost(choice *routeChoice) float64 {\n\tp := choice.probability\n\tif p < 0.005 {\n\t\tp = 0.005\n\t}\n\n\t// The logarithmic term maximizes the chance that every planned shard\n\t// settles. The reciprocal term mildly prices the expected retry count.\n\treturn -math.Log(p) + 0.035/p +\n\t\tfloat64(choice.fee)/2_000_000\n}\n\nfunc (r *candidateRouter) forecastPlan(total lnwire.MilliSatoshi,\n\tpartsLeft uint32, first *routeChoice) (float64, bool) {\n\n\tif first.deliver <= 0 || first.deliver > total {\n\t\treturn 0, false\n\t}\n\n\tsnapshot := copyReservations(r.reserved)\n\tdefer restoreReservations(r.reserved, snapshot)\n\n\treserveChoice(r.reserved, first)\n\tremaining := total - first.deliver\n\tscore := choiceCost(first) + 0.025\n\tused := uint32(1)\n\n\tfor remaining > 0 {\n\t\tif used >= partsLeft {\n\t\t\treturn 0, false\n\t\t}\n\n\t\tslots := partsLeft - used\n\t\tshard := ceilDivide(remaining, slots)\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\t// Try to place more on an available corridor so that later\n\t\t\t// slots are not forced below their channel minimums.\n\t\t\tfound := false\n\t\t\tfor divisor := uint32(2); divisor <= slots; divisor++ {\n\t\t\t\tprobe := ceilDivide(remaining, divisor)\n\t\t\t\tchoice, err = r.findRoute(probe)\n\t\t\t\tif err == nil {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\treturn 0, false\n\t\t\t}\n\t\t}\n\n\t\treserveChoice(r.reserved, choice)\n\t\tremaining -= choice.deliver\n\t\tscore += choiceCost(choice) + 0.025\n\t\tused++\n\t}\n\n\t// Prefer fewer parts only when end-to-end reliability is comparable.\n\tscore += 0.012 * float64(used*used)\n\n\treturn score, true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif r.spec.MaxParts == 0 || inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\tif r.attempts >= maxRouteAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tvar best *routeChoice\n\tbestScore := math.Inf(1)\n\tsearches := 0\n\n\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\tif searches >= maxForecastSearch {\n\t\t\tbreak\n\t\t}\n\n\t\tchoice, err := r.findRoute(shard)\n\t\tsearches++\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tscore, feasible := r.forecastPlan(amt, partsLeft, choice)\n\t\tif !feasible {\n\t\t\tcontinue\n\t\t}\n\n\t\tif best == nil || score < bestScore {\n\t\t\tbest = choice\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\t// Preserve a reliable terminal fallback when the bounded joint\n\t\t// search cannot construct a complete portfolio.\n\t\tfor _, shard := range r.candidateAmounts(amt, partsLeft) {\n\t\t\tchoice, err := r.findRoute(shard)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := choiceCost(choice)\n\t\t\tif best == nil || score < bestScore {\n\t\t\t\tbest = choice\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treserveChoice(r.reserved, best)\n\tr.attempts++\n\n\treturn best.route, nil\n}\n\nfunc routeEdgeData(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\n\n\tkeys := make([]edgeKey, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkeys[i] = edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) storeBelief(key edgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = amt / 3\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + amt/3) / 3\n\t}\n\n\tif belief.lowerOK >= belief.upperFail {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnForwarded(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && belief.upperFail <= amt {\n\t\tbelief.upperFail = 0\n\t}\n\n\toptimistic := edge.capacity * 9 / 10\n\tif belief.estimate < amt {\n\t\tbelief.estimate = amt\n\t}\n\tif belief.estimate < optimistic {\n\t\tbelief.estimate = (belief.estimate + optimistic) / 2\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n}\n\nfunc subtractFloor(value, amount,\n\tfloor lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= floor+amount {\n\t\treturn floor\n\t}\n\n\treturn value - amount\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) learnSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edges[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\testimate := maxMSat(belief.estimate, amt)\n\toptimistic := edge.capacity * 9 / 10\n\tif estimate < optimistic {\n\t\testimate = (estimate + optimistic) / 2\n\t}\n\n\tbelief.estimate = subtractFloor(estimate, amt, 0)\n\tbelief.lowerOK = subtractFloor(\n\t\tmaxMSat(belief.lowerOK, amt), amt, 0,\n\t)\n\tif belief.upperFail > 0 {\n\t\tbelief.upperFail = subtractFloor(\n\t\t\tbelief.upperFail, amt, 1,\n\t\t)\n\t}\n\n\tbelief.samples++\n\tr.storeBelief(key, belief)\n\n\treverse := edgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edges[reverse]\n\tif reverseEdge == nil {\n\t\treturn\n\t}\n\n\treverseBelief := r.beliefs[reverse]\n\treverseBelief.estimate += amt\n\tif reverseBelief.estimate > reverseEdge.capacity {\n\t\treverseBelief.estimate = reverseEdge.capacity\n\t}\n\n\tif reverseBelief.lowerOK > 0 {\n\t\treverseBelief.lowerOK += amt\n\t\tif reverseBelief.lowerOK > reverseEdge.capacity {\n\t\t\treverseBelief.lowerOK = reverseEdge.capacity\n\t\t}\n\t}\n\n\tif reverseBelief.upperFail > 0 {\n\t\treverseBelief.upperFail += amt\n\t\tif reverseBelief.upperFail > reverseEdge.capacity {\n\t\t\treverseBelief.upperFail = 0\n\t\t}\n\t}\n\n\treverseBelief.samples++\n\tr.storeBelief(reverse, reverseBelief)\n}\n\nfunc (r *candidateRouter) releaseReservations(keys []edgeKey,\n\tamounts []lnwire.MilliSatoshi) {\n\n\tfor i, key := range keys {\n\t\tvalue := r.reserved[key]\n\t\tif value <= amounts[i] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] = value - amounts[i]\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"attempt route is empty\")\n\t}\n\n\tkeys, amounts := routeEdgeData(rt)\n\tr.releaseReservations(keys, amounts)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\tr.learnSuccess(key, amounts[i])\n\n\t\t\tif key.from == r.source {\n\t\t\t\tbalance := r.localBalances[key.chanID]\n\t\t\t\tif amounts[i] >= balance {\n\t\t\t\t\tr.localBalances[key.chanID] = 0\n\t\t\t\t} else {\n\t\t\t\t\tr.localBalances[key.chanID] =\n\t\t\t\t\t\tbalance - amounts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r.edgeFailures[key] > 0 {\n\t\t\t\tr.edgeFailures[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tr.lastFailedAmt = rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\t// Unknown failures receive only a small path-level penalty. They do\n\t\t// not justify poisoning every channel on the route.\n\t\tfor _, key := range keys {\n\t\t\tif r.edgeFailures[key] < 2 {\n\t\t\t\tr.edgeFailures[key]++\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Every channel before the failing outgoing channel accepted the HTLC.\n\t// The failed attempt rolls back, so this is a liquidity observation\n\t// without the balance shift associated with settlement.\n\tfor i := 0; i < failIndex; i++ {\n\t\tr.learnForwarded(keys[i], amounts[i])\n\t\tif r.edgeFailures[keys[i]] > 0 {\n\t\t\tr.edgeFailures[keys[i]]--\n\t\t}\n\t}\n\n\tkey := keys[failIndex]\n\tfailedAmt := amounts[failIndex]\n\tr.edgeFailures[key] += 2\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, failedAmt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.broken[key] = true\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 8,
|
|
"parent": 1,
|
|
"score": 0.5085,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\triskCostMsat = 280000.0\n\n\tlowerRetryFactor = 0.62\n\tminShardMsat = lnwire.MilliSatoshi(10_000)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tfailures uint16\n\tsuccess uint16\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tmu sync.Mutex\n\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tbaseShard lnwire.MilliSatoshi\n\tlastRemaining lnwire.MilliSatoshi\n\tconsecutiveFailures uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tparts := uint32(1)\n\tswitch {\n\tcase spec.Amount > 1_000_000_000:\n\t\tparts = 8\n\tcase spec.Amount > 400_000_000:\n\t\tparts = 6\n\tcase spec.Amount > 100_000_000:\n\t\tparts = 4\n\tcase spec.Amount > 25_000_000:\n\t\tparts = 2\n\t}\n\tif spec.MaxParts != 0 && parts > spec.MaxParts {\n\t\tparts = spec.MaxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tr.baseShard = ceilDiv(\n\t\tspec.Amount, lnwire.MilliSatoshi(parts),\n\t)\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < minProbability:\n\t\treturn minProbability\n\tcase p > maxProbability:\n\t\treturn maxProbability\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.51 * math.Exp(-x/0.027)\n\thighMode := 0.47 / (1 + math.Exp(15*(x-0.80)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) localAvailable(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := r.localBalances[edge.key.chanID]\n\tspent := r.localSpent[edge.key.chanID]\n\treserved := r.reserved[edge.key]\n\n\tif spent >= available {\n\t\treturn 0\n\t}\n\tavailable -= spent\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif amt > r.localAvailable(edge) {\n\t\t\treturn minProbability\n\t\t}\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.055, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\tif belief.failures > belief.success {\n\t\tweight := 0.72\n\t\tif belief.failures >= 3 {\n\t\t\tweight = 0.84\n\t\t}\n\t\treturn clampProbability(\n\t\t\t(1-weight)*prior + weight*point,\n\t\t)\n\t}\n\n\treturn clampProbability(0.48*prior + 0.52*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbestScore, exists := dist[item.node]\n\t\tif !exists || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif item.arriving != required[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif reserved >= edge.capacity ||\n\t\t\t\tamtOver > edge.capacity-reserved {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tamtOver > r.localAvailable(edge) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tif belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\t\t\tpenalty := r.routePenalty[edge.key]\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tpenalty + 200\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, exists := r.edgeByKey[key]\n\treturn key, exists\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeFee(rt *route.Route) lnwire.MilliSatoshi {\n\tdelivered := deliveredAmount(rt)\n\tif rt.TotalAmount <= delivered {\n\t\treturn 0\n\t}\n\treturn rt.TotalAmount - delivered\n}\n\nfunc (r *candidateRouter) routeProbability(rt *route.Route) float64 {\n\tprobability := 1.0\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\treturn minProbability\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tprobability *= r.probability(\n\t\t\tedge, routeAmount(rt, i),\n\t\t)\n\t}\n\treturn probability\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendShard(values []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn values\n\t}\n\n\tseen[value] = true\n\treturn append(values, value)\n}\n\nfunc (r *candidateRouter) shardCandidates(amt,\n\tminimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 32)\n\n\tvalues = appendShard(values, seen, amt, minimum, amt)\n\tvalues = appendShard(\n\t\tvalues, seen, r.baseShard, minimum, amt,\n\t)\n\n\tfractions := []float64{\n\t\t0.82, 0.68, 0.56, 0.46, 0.38, 0.31, 0.25,\n\t\t0.20, 0.16, 0.125,\n\t}\n\tfor _, fraction := range fractions {\n\t\tvalue := lnwire.MilliSatoshi(\n\t\t\tfloat64(amt) * fraction,\n\t\t)\n\t\tvalues = appendShard(\n\t\t\tvalues, seen, value, minimum, amt,\n\t\t)\n\t}\n\n\tfor _, edge := range r.edgeByKey {\n\t\tvar limit lnwire.MilliSatoshi\n\t\tswitch {\n\t\tcase edge.key.from == r.source:\n\t\t\tlimit = r.localAvailable(edge)\n\n\t\tcase r.beliefs[edge.key].lowerOK != 0:\n\t\t\tlimit = r.beliefs[edge.key].lowerOK\n\n\t\tcase r.beliefs[edge.key].estimate != 0:\n\t\t\tlimit = r.beliefs[edge.key].estimate * 9 / 10\n\n\t\tcase r.beliefs[edge.key].upperBad != 0:\n\t\t\tlimit = r.beliefs[edge.key].upperBad * 3 / 5\n\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\tvalues = appendShard(\n\t\t\tvalues, seen, limit, minimum, amt,\n\t\t)\n\t\tvalues = appendShard(\n\t\t\tvalues, seen, limit*4/5, minimum, amt,\n\t\t)\n\t}\n\n\tvalues = appendShard(\n\t\tvalues, seen, minimum, minimum, amt,\n\t)\n\tsort.Slice(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.consecutiveFailures = 0\n\t}\n\tr.lastRemaining = amt\n\n\tif r.spec.MaxParts != 0 &&\n\t\tinFlightHtlcs >= r.spec.MaxParts {\n\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tminimum := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\tif minimum < minShardMsat {\n\t\tminimum = minShardMsat\n\t}\n\tif minimum > amt {\n\t\tminimum = amt\n\t}\n\n\tcandidates := r.shardCandidates(amt, minimum)\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestUtility = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range candidates {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := r.routeProbability(rt)\n\t\tfee := float64(routeFee(rt))\n\t\tsizeGain := math.Log1p(\n\t\t\tfloat64(shard) / math.Max(float64(minimum), 1),\n\t\t)\n\n\t\tutility := 1.35*sizeGain +\n\t\t\t0.42*math.Log(math.Max(probability, 1e-12)) -\n\t\t\tfee/2_000_000\n\n\t\tif shard == amt {\n\t\t\tutility += 0.25\n\t\t}\n\t\tif probability >= 0.09 {\n\t\t\tutility += 0.18\n\t\t}\n\t\tif utility > bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbestRoute = rt\n\t\t}\n\n\t\tif shard > minimum && probability >= 0.22 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\tr.reserve(bestRoute)\n\treturn bestRoute, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tedge := r.edgeByKey[key]\n\tif edge != nil {\n\t\tif belief.lowerOK > edge.capacity {\n\t\t\tbelief.lowerOK = edge.capacity\n\t\t}\n\t\tif belief.estimate > edge.capacity {\n\t\t\tbelief.estimate = edge.capacity\n\t\t}\n\t\tif belief.upperBad > edge.capacity {\n\t\t\tbelief.upperBad = edge.capacity\n\t\t}\n\t}\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordPass(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tremaining := edge.capacity - amt\n\tinferred := amt + remaining*4/5\n\tif inferred > belief.estimate {\n\t\tbelief.estimate = inferred\n\t}\n\tif belief.success < math.MaxUint16 {\n\t\tbelief.success++\n\t}\n\tif belief.failures > 0 {\n\t\tbelief.failures--\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc subtractFloor(value,\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif amt >= value {\n\t\treturn 0\n\t}\n\treturn value - amt\n}\n\nfunc (r *candidateRouter) recordSettlement(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil {\n\t\treturn\n\t}\n\tif key.from == r.source {\n\t\tr.localSpent[key.chanID] += amt\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tremaining := edge.capacity - amt\n\tinferredPre := amt + remaining*4/5\n\tif inferredPre > belief.estimate {\n\t\tbelief.estimate = inferredPre\n\t}\n\n\tbelief.lowerOK = subtractFloor(belief.lowerOK, amt)\n\tbelief.estimate = subtractFloor(belief.estimate, amt)\n\tif belief.upperBad != 0 {\n\t\tbelief.upperBad = subtractFloor(\n\t\t\tbelief.upperBad, amt,\n\t\t)\n\t\tif belief.upperBad == 0 {\n\t\t\tbelief.upperBad = 1\n\t\t}\n\t}\n\tif belief.success < math.MaxUint16 {\n\t\tbelief.success++\n\t}\n\tif belief.failures > 0 {\n\t\tbelief.failures--\n\t}\n\tr.saveBelief(key, belief)\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseEdge := r.edgeByKey[reverseKey]\n\tif reverseEdge == nil || reverseKey.from == r.source {\n\t\treturn\n\t}\n\n\treverse := r.beliefs[reverseKey]\n\tif reverse.estimate > reverseEdge.capacity-amt {\n\t\treverse.estimate = reverseEdge.capacity\n\t} else {\n\t\treverse.estimate += amt\n\t}\n\tif reverse.lowerOK != 0 {\n\t\tif reverse.lowerOK > reverseEdge.capacity-amt {\n\t\t\treverse.lowerOK = reverseEdge.capacity\n\t\t} else {\n\t\t\treverse.lowerOK += amt\n\t\t}\n\t}\n\tif reverse.upperBad != 0 {\n\t\tif reverse.upperBad > reverseEdge.capacity-amt {\n\t\t\treverse.upperBad = 0\n\t\t} else {\n\t\t\treverse.upperBad += amt\n\t\t}\n\t}\n\tr.saveBelief(reverseKey, reverse)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt / 5\n\tdepletedEstimate := edge.capacity / 25\n\tif estimate > depletedEstimate {\n\t\testimate = depletedEstimate\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\tif belief.failures < math.MaxUint16 {\n\t\tbelief.failures++\n\t}\n\tif belief.success > 0 {\n\t\tbelief.success--\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif !exists {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSettlement(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.18\n\t\t}\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tif delivered > 0 {\n\t\t\tremaining := r.lastRemaining - delivered\n\t\t\tif remaining > 0 {\n\t\t\t\ttarget := delivered\n\t\t\t\tif target > remaining {\n\t\t\t\t\ttarget = remaining\n\t\t\t\t}\n\t\t\t\tr.baseShard = target\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif exists {\n\t\t\t\tr.routePenalty[key] += riskCostMsat / 3\n\t\t\t}\n\t\t}\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.recordPass(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.7\n\t\t}\n\t}\n\n\tkey, exists := r.routeEdge(rt, failIndex)\n\tif !exists {\n\t\tr.consecutiveFailures++\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tfailedAmt := routeAmount(rt, failIndex)\n\t\tr.recordFailure(key, failedAmt)\n\t\tr.routePenalty[key] += riskCostMsat * 1.4\n\t}\n\n\tr.consecutiveFailures++\n\n\tdelivered := deliveredAmount(rt)\n\tretry := lnwire.MilliSatoshi(\n\t\tfloat64(delivered) * lowerRetryFactor,\n\t)\n\tif retry > 0 && retry < r.baseShard {\n\t\tr.baseShard = retry\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 9,
|
|
"parent": 1,
|
|
"score": -0.1122,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = uint32(40)\n\n\triskCostMsat = 260000.0\n\tminProbability = 0.003\n\tmaxProbability = 0.995\n\n\tlowerRetryFactor = 0.62\n\tmaxShardFactor = 2.20\n\tmaxShardProbes = 28\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tvalues map[candidateEdgeKey]liquidityBelief\n}{\n\tvalues: make(map[candidateEdgeKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]liquidityBelief\n\tblocked map[candidateEdgeKey]bool\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\n\tlastRemaining lnwire.MilliSatoshi\n\tretryHint lnwire.MilliSatoshi\n\tfailureCount uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]liquidityBelief),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tlastRemaining: spec.Amount,\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key, belief := range sharedBeliefs.values {\n\t\tif _, exists := r.edgeByKey[key]; exists {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc ceilDiv(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b <= 0 {\n\t\treturn a\n\t}\n\treturn (a + b - 1) / b\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < minProbability:\n\t\treturn minProbability\n\tcase p > maxProbability:\n\t\treturn maxProbability\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn minProbability\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.50 * math.Exp(-x/0.024)\n\thighMode := 0.48 / (1 + math.Exp(15*(x-0.80)))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) available(edge *candidateEdge) lnwire.MilliSatoshi {\n\tavailable := edge.capacity\n\tif edge.key.from == r.source {\n\t\tavailable = r.localBalances[edge.key.chanID]\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved >= available {\n\t\treturn 0\n\t}\n\treturn available - reserved\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif amt > r.available(edge) {\n\t\treturn minProbability\n\t}\n\tif edge.key.from == r.source {\n\t\treturn maxProbability\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\treturn minProbability\n\t}\n\tif belief.lowerOK != 0 && amt <= belief.lowerOK {\n\t\treturn maxProbability\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tif belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\twidth := math.Max(float64(edge.capacity)*0.065, 1)\n\tpoint := 1 / (1 + math.Exp(\n\t\t(float64(amt)-float64(belief.estimate))/width,\n\t))\n\n\treturn clampProbability(0.52*prior + 0.48*point)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(value any) {\n\t*q = append(*q, value.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoutePass(amt lnwire.MilliSatoshi,\n\tallowKnownBad bool) (*route.Route, error) {\n\n\tdist := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\trequired := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\n\t\tbestScore, exists := dist[item.node]\n\t\tif !exists || item.score > bestScore {\n\t\t\tcontinue\n\t\t}\n\t\tif required[item.node] != item.arriving {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.blocked[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) || amtOver > r.available(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbelief := r.beliefs[edge.key]\n\t\t\tknownBad := belief.upperBad != 0 &&\n\t\t\t\tamtOver >= belief.upperBad\n\t\t\tif knownBad && !allowKnownBad {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tif fee < 0 || sending > math.MaxInt64-fee {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, amtOver)\n\t\t\trisk := -math.Log(probability) * riskCostMsat\n\n\t\t\tstaleProbePenalty := 0.0\n\t\t\tif knownBad {\n\t\t\t\tstaleProbePenalty = riskCostMsat * 2.4\n\t\t\t}\n\n\t\t\tcapacityBonus := 0.0\n\t\t\tif edge.capacity > 0 {\n\t\t\t\tspare := float64(r.available(edge)-amtOver) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tif spare > 0 {\n\t\t\t\t\tcapacityBonus = math.Min(\n\t\t\t\t\t\t45000, spare*18000,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) + risk +\n\t\t\t\tr.routePenalty[edge.key] + staleProbePenalty +\n\t\t\t\t250 - capacityBonus\n\n\t\t\toldScore, seen := dist[edge.key.from]\n\t\t\tif seen && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\trequired[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, exists := next[r.source]; !exists {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\trt, err := r.findRoutePass(amt, false)\n\tif err == nil {\n\t\treturn rt, nil\n\t}\n\n\t// Hard liquidity bounds are intentionally retained across payments.\n\t// If they eliminate every path, probe the least costly stale path once\n\t// instead of terminally giving up without refreshing the evidence.\n\treturn r.findRoutePass(amt, true)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, exists := next[node]\n\t\tif !exists {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t\tif len(path) > len(r.edgeByKey) {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tfee := outgoing.fee(amtOver[i+1])\n\t\tif fee < 0 || amtOver[i+1] > math.MaxInt64-fee {\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc (r *candidateRouter) routeEdge(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\tkey := candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n\t_, exists := r.edgeByKey[key]\n\n\treturn key, exists\n}\n\nfunc routeAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc deliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.reserved[key] += routeAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := routeAmount(rt, i)\n\t\tif amt >= r.reserved[key] {\n\t\t\tdelete(r.reserved, key)\n\t\t} else {\n\t\t\tr.reserved[key] -= amt\n\t\t}\n\t}\n}\n\nfunc appendProbe(probes []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn probes\n\t}\n\n\tseen[value] = true\n\treturn append(probes, value)\n}\n\nfunc (r *candidateRouter) shardProbes(remaining,\n\tminimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tmaximum := remaining\n\tif minimum < remaining {\n\t\tsoftMax := lnwire.MilliSatoshi(\n\t\t\tfloat64(minimum) * maxShardFactor,\n\t\t)\n\t\tif softMax > minimum && softMax < maximum {\n\t\t\tmaximum = softMax\n\t\t}\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tprobes := make([]lnwire.MilliSatoshi, 0, maxShardProbes)\n\n\tprobes = appendProbe(probes, seen, minimum, minimum, maximum)\n\tfor _, factor := range []float64{\n\t\t1.12, 1.28, 1.48, 1.72, 2.00, 2.20,\n\t} {\n\t\tprobes = appendProbe(\n\t\t\tprobes, seen,\n\t\t\tlnwire.MilliSatoshi(float64(minimum)*factor),\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tif r.retryHint != 0 {\n\t\tprobes = appendProbe(\n\t\t\tprobes, seen, r.retryHint, minimum, maximum,\n\t\t)\n\t}\n\n\tfor _, belief := range r.beliefs {\n\t\tif belief.lowerOK != 0 {\n\t\t\tfor _, factor := range []float64{0.96, 0.82} {\n\t\t\t\tprobes = appendProbe(\n\t\t\t\t\tprobes, seen,\n\t\t\t\t\tlnwire.MilliSatoshi(\n\t\t\t\t\t\tfloat64(belief.lowerOK) *\n\t\t\t\t\t\t\tfactor,\n\t\t\t\t\t),\n\t\t\t\t\tminimum, maximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tif belief.upperBad != 0 {\n\t\t\tfor _, factor := range []float64{0.72, 0.55} {\n\t\t\t\tprobes = appendProbe(\n\t\t\t\t\tprobes, seen,\n\t\t\t\t\tlnwire.MilliSatoshi(\n\t\t\t\t\t\tfloat64(belief.upperBad) *\n\t\t\t\t\t\t\tfactor,\n\t\t\t\t\t),\n\t\t\t\t\tminimum, maximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tprobes = appendProbe(\n\t\tprobes, seen, maximum, minimum, maximum,\n\t)\n\tsort.Slice(probes, func(i, j int) bool {\n\t\treturn probes[i] < probes[j]\n\t})\n\n\tif len(probes) <= maxShardProbes {\n\t\treturn probes\n\t}\n\n\ttrimmed := make([]lnwire.MilliSatoshi, 0, maxShardProbes)\n\tfor i, probe := range probes {\n\t\tif len(trimmed) == maxShardProbes {\n\t\t\tbreak\n\t\t}\n\n\t\tremainingSlots := maxShardProbes - len(trimmed)\n\t\tremainingItems := len(probes) - i\n\t\tif i == 0 || i == len(probes)-1 ||\n\t\t\tremainingItems <= remainingSlots ||\n\t\t\ti%(len(probes)/maxShardProbes+1) == 0 {\n\n\t\t\ttrimmed = append(trimmed, probe)\n\t\t}\n\t}\n\n\treturn trimmed\n}\n\nfunc (r *candidateRouter) routeQuality(rt *route.Route,\n\tdelivered, minimum lnwire.MilliSatoshi) float64 {\n\n\tlogProbability := 0.0\n\tfor i := range rt.Hops {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif !exists {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tedge := r.edgeByKey[key]\n\t\tlogProbability += math.Log(\n\t\t\tr.probability(edge, routeAmount(rt, i)),\n\t\t)\n\t}\n\n\tfee := rt.TotalAmount - delivered\n\tsizeGain := math.Log(\n\t\tmath.Max(1, float64(delivered)/float64(minimum)),\n\t)\n\n\t// Reliability dominates. The size term rewards a proven high-liquidity\n\t// corridor enough to create unequal MPP shards, while avoiding a blind\n\t// preference for the largest gossip-feasible amount.\n\treturn logProbability + 0.34*sizeGain -\n\t\tfloat64(fee)/riskCostMsat\n}\n\nfunc (r *candidateRouter) chooseRoute(remaining,\n\tminimum lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tprobes := r.shardProbes(remaining, minimum)\n\n\tvar (\n\t\tbest *route.Route\n\t\tbestQuality = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range probes {\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tquality := r.routeQuality(rt, shard, minimum)\n\t\tif best == nil || quality > bestQuality {\n\t\t\tbest = rt\n\t\t\tbestQuality = quality\n\t\t}\n\t}\n\n\tif best != nil {\n\t\treturn best, nil\n\t}\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tif amt < r.lastRemaining {\n\t\tr.failureCount = 0\n\t\tr.retryHint = 0\n\t}\n\tr.lastRemaining = amt\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tminimum := ceilDiv(\n\t\tamt, lnwire.MilliSatoshi(partsLeft),\n\t)\n\tif minimum <= 0 || minimum > amt {\n\t\tminimum = amt\n\t}\n\n\trt, err := r.chooseRoute(amt, minimum)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.reserve(rt)\n\treturn rt, nil\n}\n\ntype codedFailure interface {\n\tCode() lnwire.FailCode\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.values[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperBad != 0 && amt >= belief.upperBad {\n\t\tbelief.upperBad = 0\n\t}\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\testimate := amt\n\tif edge.capacity > amt {\n\t\testimate += (edge.capacity - amt) * 4 / 5\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tedge := r.edgeByKey[key]\n\tif edge == nil || key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\tif belief.upperBad == 0 || amt < belief.upperBad {\n\t\tbelief.upperBad = amt\n\t}\n\n\testimate := amt * 28 / 100\n\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\tif belief.lowerOK > belief.estimate {\n\t\tbelief.estimate = belief.lowerOK\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tr.release(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif !exists {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.20\n\t\t}\n\n\t\tr.failureCount = 0\n\t\tr.retryHint = 0\n\t\treturn nil\n\t}\n\n\tfailIndex := r.failureIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, exists := r.routeEdge(rt, i)\n\t\t\tif exists {\n\t\t\t\tr.routePenalty[key] += riskCostMsat * 0.45\n\t\t\t}\n\t\t}\n\n\t\tr.failureCount++\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < failIndex; i++ {\n\t\tkey, exists := r.routeEdge(rt, i)\n\t\tif exists {\n\t\t\tr.recordSuccess(key, routeAmount(rt, i))\n\t\t\tr.routePenalty[key] *= 0.65\n\t\t}\n\t}\n\n\tkey, exists := r.routeEdge(rt, failIndex)\n\tif !exists {\n\t\treturn nil\n\t}\n\n\tcode := lnwire.FailCode(0)\n\tif failure, ok := result.Failure.(codedFailure); ok {\n\t\tcode = failure.Code()\n\t}\n\n\tfailedAmount := routeAmount(rt, failIndex)\n\tswitch code {\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\n\tdefault:\n\t\tr.recordFailure(key, failedAmount)\n\t\tr.routePenalty[key] += riskCostMsat\n\n\t\tdelivered := deliveredAmount(rt)\n\t\tretry := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * lowerRetryFactor,\n\t\t)\n\t\tif retry > 0 &&\n\t\t\t(r.retryHint == 0 || retry < r.retryHint) {\n\n\t\t\tr.retryHint = retry\n\t\t}\n\t}\n\n\tr.failureCount++\n\tif r.failureCount >= 3 {\n\t\tr.routePenalty[key] += riskCostMsat * 0.35\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
}
|
|
]
|
|
} |