lnd/simulation/command-center/data/run.json
Olaoluwa Osuntokun ecddf094bf simulation: refresh dashboard for code_gen2 run
In this commit, we pick up the dashboard refresh for the new code_gen2
run: the exported lineage data and a live-run note that the small-seed,
insight-enriched follow-up is in flight.
2026-07-24 13:07:51 -07:00

56 lines
No EOL
64 KiB
JSON

{
"run_id": "code_gen2",
"reflection_lm": "codex:gpt-5.6-sol",
"mode": "generalization",
"status": "complete",
"seed_score": 0.4123,
"best_score": 0.9701,
"iterations": [
{
"i": 0,
"candidate_score": 0.4123,
"best_score": 0.4123,
"note": "seed"
},
{
"i": 1,
"candidate_score": 0.4933,
"best_score": 0.4933,
"note": "accepted"
}
],
"seed_params": {
"source": "package main\n\n// This file is the CANDIDATE SLOT for evolved routing algorithms. During\n// optimization, the entire file is replaced (via go build -overlay) with a\n// generated implementation. The contract is a single constructor:\n//\n//\tnewCandidateRouter(view, source, localBalances, spec)\n//\n// returning a routing.SimRouter. The router sees only the public gossip\n// graph, its own channel balances and per-attempt feedback \u2014 the same\n// information a real Lightning sender has. The in-tree implementation below\n// is the seed algorithm: a deliberately simple fee-optimizing Dijkstra with\n// failure blacklisting and halving-based MPP splitting.\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\n// candidateEdge is one directed edge of the public graph: a channel from\n// one node to another, with the policy the sending node announced.\ntype candidateEdge struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\n// fee returns the fee the sending node charges to forward amt over this\n// edge.\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\n// usable reports whether the edge can carry the given amount per its\n// announced policy.\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\t// The public capacity is a hard upper bound on what can flow.\n\treturn amt <= e.capacity\n}\n\n// candidateRouter is the seed algorithm: cheapest-path routing with a\n// failure blacklist and amount halving when no route is found.\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\t// incomingEdges maps a node to the directed edges arriving at it,\n\t// the natural shape for backward Dijkstra.\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\n\t// localBalances is the exact outbound liquidity of our own channels.\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// failedAmt records, per directed channel, the lowest amount that\n\t// failed with a liquidity error; routes are built to stay below it.\n\tfailedAmt map[uint64]lnwire.MilliSatoshi\n\n\t// shardAmt is the current shard size for MPP splitting.\n\tshardAmt lnwire.MilliSatoshi\n\n\t// partsUsed counts the successful shards so far.\n\tpartsUsed uint32\n\n\t// pending maps in-flight attempt ids to their routes.\n\tpending map[uint64]*route.Route\n}\n\n// newCandidateRouter builds the router for one payment. This signature is\n// the stable contract between the harness and generated candidates.\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\trouter := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tfailedAmt: make(map[uint64]lnwire.MilliSatoshi),\n\t\tshardAmt: spec.Amount,\n\t\tpending: make(map[uint64]*route.Route),\n\t}\n\n\t// Build the adjacency list from gossip. Iterating a node's channels\n\t// yields, per channel, the policy the OTHER node announced toward us\n\t// (InPolicy). That is exactly the policy governing the directed edge\n\t// other -> node, so we record the reversed edge at each visit.\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(ctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\trouter.incomingEdges[edge.to] = append(\n\t\t\t\t\trouter.incomingEdges[edge.to], edge,\n\t\t\t\t)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn router, nil\n}\n\n// dijkstraItem is a priority queue entry.\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tcost lnwire.MilliSatoshi\n\tidx int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int { return len(q) }\nfunc (q dijkstraQueue) Less(i, j int) bool { return q[i].cost < q[j].cost }\nfunc (q dijkstraQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i]; q[i].idx = i; q[j].idx = j }\nfunc (q *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\n// findRoute computes the cheapest usable path delivering amt to the target,\n// walking backward from the target so fees accumulate correctly.\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (*route.Route,\n\terror) {\n\n\t// dist[node] = amount that must arrive at node to deliver amt.\n\tdist := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = amt\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{node: r.spec.Target, cost: amt})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode, arriving := item.node, item.cost\n\n\t\tif arriving > dist[node] {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\t// Consider all edges INTO node: for edge u->node, u must\n\t\t// send arriving plus u's fee.\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tamtOver := arriving\n\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Skip channels whose liquidity failure bound says\n\t\t\t// this amount cannot pass.\n\t\t\tif bound, ok := r.failedAmt[edge.chanID]; ok &&\n\t\t\t\tamtOver >= bound {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Our own channels: check exact local balance.\n\t\t\tif edge.from == r.source {\n\t\t\t\tif r.localBalances[edge.chanID] < amtOver {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar sending lnwire.MilliSatoshi\n\t\t\tif edge.from == r.source {\n\t\t\t\t// We pay no fee to ourselves.\n\t\t\t\tsending = amtOver\n\t\t\t} else {\n\t\t\t\tsending = amtOver + edge.fee(amtOver)\n\t\t\t}\n\n\t\t\tbest, ok := dist[edge.from]\n\t\t\tif !ok || sending < best {\n\t\t\t\tdist[edge.from] = sending\n\t\t\t\tnext[edge.from] = edge\n\t\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\t\tnode: edge.from,\n\t\t\t\t\tcost: sending,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\n// buildRoute walks the next-pointers from source to target and constructs a\n// route with correctly accumulated fees and cltv deltas.\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta = 40\n\n\t// Collect the path edges source -> target.\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\t// Amounts and expiries per channel, computed backward.\n\tnumHops := len(path)\n\tamtOver := make([]lnwire.MilliSatoshi, numHops)\n\texpiryOver := make([]uint32, numHops)\n\n\tamtOver[numHops-1] = amt\n\texpiryOver[numHops-1] = finalCltvDelta\n\n\tfor i := numHops - 2; i >= 0; i-- {\n\t\tfwd := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] + fwd.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(fwd.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, numHops)\n\tfor i, edge := range path {\n\t\tamtToFwd := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < numHops-1 {\n\t\t\tamtToFwd = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amtToFwd,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\n// RequestRoute returns the next route to try: the cheapest path for the\n// current shard size, halving the shard when no route exists.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif r.shardAmt > amt {\n\t\tr.shardAmt = amt\n\t}\n\n\tfor {\n\t\trt, err := r.findRoute(r.shardAmt)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\t// No route at this shard size: split if we're allowed more\n\t\t// parts and the shard is still meaningfully large.\n\t\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\t\tif partsLeft <= 1 || r.shardAmt < 10_000_000 {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.shardAmt /= 2\n\t}\n}\n\n// ReportAttempt learns from an attempt: liquidity failures set an upper\n// bound on the failing channel.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\treturn nil\n\t}\n\n\t// Locate the failing hop and record the amount bound on its\n\t// outgoing channel.\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\tfailIdx = i + 1\n\t\t}\n\t}\n\n\t// The failing node could not forward over its outgoing channel,\n\t// which is rt.Hops[failIdx].\n\tif failIdx >= 0 && failIdx < len(rt.Hops) {\n\t\thop := rt.Hops[failIdx]\n\t\tamtOver := rt.TotalAmount\n\t\tif failIdx > 0 {\n\t\t\tamtOver = rt.Hops[failIdx-1].AmtToForward\n\t\t}\n\n\t\tbound, ok := r.failedAmt[hop.ChannelID]\n\t\tif !ok || amtOver < bound {\n\t\t\tr.failedAmt[hop.ChannelID] = amtOver\n\t\t}\n\t}\n\n\treturn nil\n}\n"
},
"best_candidate": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst candidateFinalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\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\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype candidateLiquidityState struct {\n\tupperFail lnwire.MilliSatoshi\n\tlowerOK lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tknown bool\n\tconf float64\n\tfailures uint32\n\tsuccesses uint32\n\tblocked bool\n}\n\nvar candidateKnowledge = struct {\n\tsync.Mutex\n\tstates map[candidateEdgeKey]*candidateLiquidityState\n}{\n\tstates: make(map[candidateEdgeKey]*candidateLiquidityState),\n}\n\nfunc candidateStateSnapshot(\n\tkey candidateEdgeKey) candidateLiquidityState {\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateKnowledge.states[key]\n\tif state == nil {\n\t\treturn candidateLiquidityState{}\n\t}\n\n\treturn *state\n}\n\nfunc candidateMutableState(\n\tkey candidateEdgeKey) *candidateLiquidityState {\n\n\tstate := candidateKnowledge.states[key]\n\tif state == nil {\n\t\tstate = &candidateLiquidityState{}\n\t\tcandidateKnowledge.states[key] = state\n\t}\n\n\treturn state\n}\n\nfunc candidateRecordProbe(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) {\n\n\tif edge == nil || amt <= 0 {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateMutableState(edge.key)\n\tif amt > state.lowerOK {\n\t\tstate.lowerOK = amt\n\t}\n\n\thighEstimate := edge.capacity * 9 / 10\n\tif highEstimate < amt {\n\t\thighEstimate = amt\n\t}\n\tif !state.known || state.estimate < amt {\n\t\tstate.estimate = highEstimate\n\t}\n\n\tif state.upperFail != 0 && amt >= state.upperFail {\n\t\tstate.upperFail = 0\n\t}\n\tif state.failures > 0 {\n\t\tstate.failures--\n\t}\n\n\tstate.known = true\n\tstate.conf = math.Max(state.conf, 0.85)\n\tstate.successes++\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: edge.key.chanID,\n\t\tfrom: edge.key.to,\n\t\tto: edge.key.from,\n\t}\n\treverse := candidateMutableState(reverseKey)\n\n\treverseUpper := edge.capacity - amt + 1\n\tif reverseUpper < 1 {\n\t\treverseUpper = 1\n\t}\n\tif reverse.upperFail == 0 || reverseUpper < reverse.upperFail {\n\t\treverse.upperFail = reverseUpper\n\t}\n\tif reverse.lowerOK >= reverse.upperFail {\n\t\treverse.lowerOK = reverse.upperFail - 1\n\t}\n}\n\nfunc candidateRecordFailure(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) {\n\n\tif edge == nil || amt <= 0 {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateMutableState(edge.key)\n\tif state.upperFail == 0 || amt < state.upperFail {\n\t\tstate.upperFail = amt\n\t}\n\n\tif state.lowerOK >= amt {\n\t\tstate.lowerOK = amt - 1\n\t}\n\n\tdepletedEstimate := amt / 8\n\tif depletedEstimate < state.lowerOK {\n\t\tdepletedEstimate = state.lowerOK\n\t}\n\tif !state.known || state.estimate > depletedEstimate {\n\t\tstate.estimate = depletedEstimate\n\t}\n\n\tstate.known = true\n\tstate.conf = math.Max(state.conf, 0.95)\n\tstate.failures++\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: edge.key.chanID,\n\t\tfrom: edge.key.to,\n\t\tto: edge.key.from,\n\t}\n\treverse := candidateMutableState(reverseKey)\n\n\thighEstimate := edge.capacity * 9 / 10\n\tif highEstimate < edge.capacity-amt {\n\t\thighEstimate = edge.capacity - amt\n\t}\n\tif !reverse.known || reverse.estimate < highEstimate {\n\t\treverse.estimate = highEstimate\n\t}\n\treverse.known = true\n\treverse.conf = math.Max(reverse.conf, 0.8)\n}\n\nfunc candidateBlockEdge(edge *candidateEdge) {\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateMutableState(edge.key)\n\tstate.blocked = true\n}\n\nfunc candidateRecordSettlement(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) {\n\n\tif edge == nil || amt <= 0 {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tforward := candidateMutableState(edge.key)\n\n\tpreEstimate := forward.estimate\n\tif !forward.known {\n\t\tpreEstimate = edge.capacity * 9 / 10\n\t}\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\n\tforward.estimate = preEstimate - amt\n\tif forward.lowerOK > amt {\n\t\tforward.lowerOK -= amt\n\t} else {\n\t\tforward.lowerOK = 0\n\t}\n\n\tif forward.upperFail > amt {\n\t\tforward.upperFail -= amt\n\t} else {\n\t\tforward.upperFail = 0\n\t}\n\n\tforward.known = true\n\tforward.conf = math.Max(forward.conf, 0.85)\n\tforward.successes++\n\tif forward.failures > 0 {\n\t\tforward.failures--\n\t}\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: edge.key.chanID,\n\t\tfrom: edge.key.to,\n\t\tto: edge.key.from,\n\t}\n\treverse := candidateMutableState(reverseKey)\n\n\tif reverse.known {\n\t\treverse.estimate += amt\n\t\tif reverse.estimate > edge.capacity {\n\t\t\treverse.estimate = edge.capacity\n\t\t}\n\t} else {\n\t\treverse.estimate = amt\n\t}\n\n\treverse.lowerOK += amt\n\tif reverse.lowerOK > edge.capacity {\n\t\treverse.lowerOK = edge.capacity\n\t}\n\tif reverse.upperFail != 0 {\n\t\treverse.upperFail += amt\n\t\tif reverse.upperFail > edge.capacity {\n\t\t\treverse.upperFail = edge.capacity\n\t\t}\n\t}\n\n\treverse.known = true\n\treverse.conf = math.Max(reverse.conf, 0.9)\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tsessionPenalty map[candidateEdgeKey]float64\n\tsessionBlocked map[candidateEdgeKey]bool\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"payment specification is nil\")\n\t}\n\n\trouter := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tsessionPenalty: make(map[candidateEdgeKey]float64),\n\t\tsessionBlocked: make(map[candidateEdgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\trouter.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\trouter.incomingEdges[key.to] = append(\n\t\t\t\t\trouter.incomingEdges[key.to], edge,\n\t\t\t\t)\n\t\t\t\trouter.edges[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn router, nil\n}\n\nfunc candidatePriorProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.capacity <= 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(amt) / float64(edge.capacity)\n\n\tlowMode := 0.45 * math.Exp(-ratio/0.025)\n\thighMode := 0.50 /\n\t\t(1 + math.Exp((ratio-0.92)/0.04))\n\n\tprobability := 0.025 + lowMode + highMode\n\tif probability > 0.985 {\n\t\tprobability = 0.985\n\t}\n\tif probability < 0.005 {\n\t\tprobability = 0.005\n\t}\n\n\treturn probability\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif r.sessionBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tstate := candidateStateSnapshot(edge.key)\n\tif state.blocked {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif r.localBalances[edge.key.chanID] < amt {\n\t\t\treturn 0\n\t\t}\n\n\t\treturn 1\n\t}\n\n\tif state.upperFail != 0 && amt >= state.upperFail {\n\t\treturn 0\n\t}\n\tif state.lowerOK >= amt {\n\t\treturn 0.995\n\t}\n\n\tprior := candidatePriorProbability(edge, amt)\n\tif !state.known {\n\t\treturn prior\n\t}\n\n\tif state.estimate >= amt {\n\t\tmargin := float64(state.estimate-amt+1) /\n\t\t\tfloat64(edge.capacity+1)\n\t\tprobability := 0.78 + 0.17*state.conf +\n\t\t\t0.04*math.Min(margin, 1)\n\n\t\tif probability > 0.995 {\n\t\t\tprobability = 0.995\n\t\t}\n\n\t\treturn probability\n\t}\n\n\tif state.upperFail != 0 {\n\t\trelative := float64(amt) /\n\t\t\tfloat64(state.upperFail)\n\t\tif relative > 1 {\n\t\t\trelative = 1\n\t\t}\n\n\t\tprobability := 0.03 +\n\t\t\t0.35*math.Pow(1-relative, 3) +\n\t\t\t0.15*prior\n\n\t\tif state.failures > state.successes+1 {\n\t\t\tprobability *= 0.75\n\t\t}\n\t\tif probability < 0.01 {\n\t\t\tprobability = 0.01\n\t\t}\n\n\t\treturn probability\n\t}\n\n\tprobability := 0.35 * prior\n\tif state.successes > state.failures {\n\t\tprobability += 0.15\n\t}\n\tif probability > 0.75 {\n\t\tprobability = 0.75\n\t}\n\tif probability < 0.01 {\n\t\tprobability = 0.01\n\t}\n\n\treturn probability\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk float64\n}\n\ntype candidateQueue []*candidateQueueItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateQueueItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\n\treturn item\n}\n\ntype candidateRouteChoice struct {\n\troute *route.Route\n\tlogRisk float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, 0, errors.New(\"route amount must be positive\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source is payment target\")\n\t}\n\n\tdist := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = 0\n\tqueue := &candidateQueue{}\n\theap.Push(queue, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t})\n\n\tvar sourceRisk float64\n\n\tfor queue.Len() != 0 {\n\t\titem := heap.Pop(queue).(*candidateQueueItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceRisk = item.risk\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOver := item.amount\n\t\t\tif !edge.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, amountOver)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amountOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tlogRisk := -math.Log(probability) +\n\t\t\t\tr.sessionPenalty[edge.key]\n\t\t\tfeePenalty := 15 * float64(fee) /\n\t\t\t\tmath.Max(float64(deliver), 1)\n\t\t\tedgeScore := logRisk + feePenalty + 0.012\n\t\t\tnewScore := item.score + edgeScore\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = newScore\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateQueueItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tamount: sending,\n\t\t\t\tscore: newScore,\n\t\t\t\trisk: item.risk + logRisk,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tbuilt, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn built, sourceRisk, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tpath := make([]*candidateEdge, 0, 8)\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"cycle in selected route\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"selected route has no hops\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamounts[last] = deliver\n\texpiries[last] = candidateFinalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tnextEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tnextEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := deliver\n\t\toutgoingExpiry := uint32(candidateFinalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tdivisor uint32) lnwire.MilliSatoshi {\n\n\tif divisor <= 1 {\n\t\treturn amt\n\t}\n\n\td := lnwire.MilliSatoshi(divisor)\n\tresult := amt / d\n\tif amt%d != 0 {\n\t\tresult++\n\t}\n\n\treturn result\n}\n\nfunc candidateShardAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tlimit := partsLeft\n\tif limit > 24 {\n\t\tlimit = 24\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, 0, limit+1)\n\tvar previous lnwire.MilliSatoshi\n\n\tfor parts := uint32(1); parts <= limit; parts++ {\n\t\tshard := candidateCeilDiv(amt, parts)\n\t\tif shard != previous {\n\t\t\tamounts = append(amounts, shard)\n\t\t\tprevious = shard\n\t\t}\n\t}\n\n\tminimum := candidateCeilDiv(amt, partsLeft)\n\tif minimum != previous {\n\t\tamounts = append(amounts, minimum)\n\t}\n\n\treturn amounts\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(\"remaining amount must be positive\")\n\t}\n\tif r.attempts >= 96 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum payment parts reached\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tshards := candidateShardAmounts(amt, partsLeft)\n\tminimum := shards[len(shards)-1]\n\n\tthreshold := 0.20\n\tif partsLeft <= 2 {\n\t\tthreshold = 0.08\n\t}\n\n\tvar fallback *candidateRouteChoice\n\tbestUtility := math.Inf(-1)\n\n\tfor _, shard := range shards {\n\t\trt, logRisk, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := math.Exp(-logRisk)\n\t\tif probability >= threshold {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tprogress := math.Log(\n\t\t\tmath.Max(float64(shard)/float64(minimum), 1),\n\t\t)\n\t\tfee := rt.TotalAmount - shard\n\t\tfeePenalty := 10 * float64(fee) /\n\t\t\tmath.Max(float64(shard), 1)\n\t\tutility := -logRisk + 0.22*progress - feePenalty\n\n\t\tif fallback == nil || utility > bestUtility {\n\t\t\tfallback = &candidateRouteChoice{\n\t\t\t\troute: rt,\n\t\t\t\tlogRisk: logRisk,\n\t\t\t}\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif fallback == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn fallback.route, nil\n}\n\nfunc (r *candidateRouter) routeEdges(\n\trt *route.Route) []*candidateEdge {\n\n\tedges := make([]*candidateEdge, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedges[i] = r.edges[key]\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn edges\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tchannelIndex int) lnwire.MilliSatoshi {\n\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[channelIndex-1].AmtToForward\n}\n\nfunc candidateFailureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tr.attempts++\n\n\tif rt == nil {\n\t\treturn errors.New(\"reported route is nil\")\n\t}\n\n\tedges := r.routeEdges(rt)\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range edges {\n\t\t\tif edge == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamount := candidateRouteAmount(rt, i)\n\t\t\tcandidateRecordSettlement(edge, amount)\n\t\t\tdelete(r.sessionPenalty, edge.key)\n\t\t}\n\n\t\tif len(rt.Hops) != 0 {\n\t\t\tfirstChan := rt.Hops[0].ChannelID\n\t\t\tspent := rt.TotalAmount\n\t\t\tif r.localBalances[firstChan] > spent {\n\t\t\t\tr.localBalances[firstChan] -= spent\n\t\t\t} else {\n\t\t\t\tr.localBalances[firstChan] = 0\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tif failIndex >= 0 {\n\t\tprefixEnd := failIndex\n\t\tif prefixEnd > len(edges) {\n\t\t\tprefixEnd = len(edges)\n\t\t}\n\n\t\tfor i := 0; i < prefixEnd; i++ {\n\t\t\tedge := edges[i]\n\t\t\tif edge == nil || edge.key.from == r.source {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcandidateRecordProbe(\n\t\t\t\tedge, candidateRouteAmount(rt, i),\n\t\t\t)\n\t\t\tdelete(r.sessionPenalty, edge.key)\n\t\t}\n\t}\n\n\tcode := result.Failure.Code()\n\tif failIndex >= 0 && failIndex < len(edges) {\n\t\tedge := edges[failIndex]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tcandidateRecordFailure(\n\t\t\t\tedge, candidateRouteAmount(rt, failIndex),\n\t\t\t)\n\n\t\t\tretryAmount := candidateRouteAmount(\n\t\t\t\trt, failIndex,\n\t\t\t) * 3 / 8\n\t\t\tif retryAmount > 0 {\n\t\t\t\tstate := candidateStateSnapshot(edge.key)\n\t\t\t\tif state.upperFail > retryAmount {\n\t\t\t\t\tr.sessionPenalty[edge.key] += 0.2\n\t\t\t\t} else {\n\t\t\t\t\tr.sessionPenalty[edge.key] += 0.8\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tcandidateBlockEdge(edge)\n\n\t\tdefault:\n\t\t\tr.sessionBlocked[edge.key] = true\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor _, edge := range edges {\n\t\tif edge == nil || edge.key.from == r.source {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.sessionPenalty[edge.key] += 0.45\n\t}\n\n\treturn nil\n}"
},
"stats": {
"evals_done": 49,
"distinct_candidates": 2
},
"candidates": [
{
"id": 0,
"parent": null,
"score": 0.4123,
"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.4933,
"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 candidateFinalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\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\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype candidateLiquidityState struct {\n\tupperFail lnwire.MilliSatoshi\n\tlowerOK lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tknown bool\n\tconf float64\n\tfailures uint32\n\tsuccesses uint32\n\tblocked bool\n}\n\nvar candidateKnowledge = struct {\n\tsync.Mutex\n\tstates map[candidateEdgeKey]*candidateLiquidityState\n}{\n\tstates: make(map[candidateEdgeKey]*candidateLiquidityState),\n}\n\nfunc candidateStateSnapshot(\n\tkey candidateEdgeKey) candidateLiquidityState {\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateKnowledge.states[key]\n\tif state == nil {\n\t\treturn candidateLiquidityState{}\n\t}\n\n\treturn *state\n}\n\nfunc candidateMutableState(\n\tkey candidateEdgeKey) *candidateLiquidityState {\n\n\tstate := candidateKnowledge.states[key]\n\tif state == nil {\n\t\tstate = &candidateLiquidityState{}\n\t\tcandidateKnowledge.states[key] = state\n\t}\n\n\treturn state\n}\n\nfunc candidateRecordProbe(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) {\n\n\tif edge == nil || amt <= 0 {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateMutableState(edge.key)\n\tif amt > state.lowerOK {\n\t\tstate.lowerOK = amt\n\t}\n\n\thighEstimate := edge.capacity * 9 / 10\n\tif highEstimate < amt {\n\t\thighEstimate = amt\n\t}\n\tif !state.known || state.estimate < amt {\n\t\tstate.estimate = highEstimate\n\t}\n\n\tif state.upperFail != 0 && amt >= state.upperFail {\n\t\tstate.upperFail = 0\n\t}\n\tif state.failures > 0 {\n\t\tstate.failures--\n\t}\n\n\tstate.known = true\n\tstate.conf = math.Max(state.conf, 0.85)\n\tstate.successes++\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: edge.key.chanID,\n\t\tfrom: edge.key.to,\n\t\tto: edge.key.from,\n\t}\n\treverse := candidateMutableState(reverseKey)\n\n\treverseUpper := edge.capacity - amt + 1\n\tif reverseUpper < 1 {\n\t\treverseUpper = 1\n\t}\n\tif reverse.upperFail == 0 || reverseUpper < reverse.upperFail {\n\t\treverse.upperFail = reverseUpper\n\t}\n\tif reverse.lowerOK >= reverse.upperFail {\n\t\treverse.lowerOK = reverse.upperFail - 1\n\t}\n}\n\nfunc candidateRecordFailure(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) {\n\n\tif edge == nil || amt <= 0 {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateMutableState(edge.key)\n\tif state.upperFail == 0 || amt < state.upperFail {\n\t\tstate.upperFail = amt\n\t}\n\n\tif state.lowerOK >= amt {\n\t\tstate.lowerOK = amt - 1\n\t}\n\n\tdepletedEstimate := amt / 8\n\tif depletedEstimate < state.lowerOK {\n\t\tdepletedEstimate = state.lowerOK\n\t}\n\tif !state.known || state.estimate > depletedEstimate {\n\t\tstate.estimate = depletedEstimate\n\t}\n\n\tstate.known = true\n\tstate.conf = math.Max(state.conf, 0.95)\n\tstate.failures++\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: edge.key.chanID,\n\t\tfrom: edge.key.to,\n\t\tto: edge.key.from,\n\t}\n\treverse := candidateMutableState(reverseKey)\n\n\thighEstimate := edge.capacity * 9 / 10\n\tif highEstimate < edge.capacity-amt {\n\t\thighEstimate = edge.capacity - amt\n\t}\n\tif !reverse.known || reverse.estimate < highEstimate {\n\t\treverse.estimate = highEstimate\n\t}\n\treverse.known = true\n\treverse.conf = math.Max(reverse.conf, 0.8)\n}\n\nfunc candidateBlockEdge(edge *candidateEdge) {\n\tif edge == nil {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tstate := candidateMutableState(edge.key)\n\tstate.blocked = true\n}\n\nfunc candidateRecordSettlement(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) {\n\n\tif edge == nil || amt <= 0 {\n\t\treturn\n\t}\n\n\tcandidateKnowledge.Lock()\n\tdefer candidateKnowledge.Unlock()\n\n\tforward := candidateMutableState(edge.key)\n\n\tpreEstimate := forward.estimate\n\tif !forward.known {\n\t\tpreEstimate = edge.capacity * 9 / 10\n\t}\n\tif preEstimate < amt {\n\t\tpreEstimate = amt\n\t}\n\n\tforward.estimate = preEstimate - amt\n\tif forward.lowerOK > amt {\n\t\tforward.lowerOK -= amt\n\t} else {\n\t\tforward.lowerOK = 0\n\t}\n\n\tif forward.upperFail > amt {\n\t\tforward.upperFail -= amt\n\t} else {\n\t\tforward.upperFail = 0\n\t}\n\n\tforward.known = true\n\tforward.conf = math.Max(forward.conf, 0.85)\n\tforward.successes++\n\tif forward.failures > 0 {\n\t\tforward.failures--\n\t}\n\n\treverseKey := candidateEdgeKey{\n\t\tchanID: edge.key.chanID,\n\t\tfrom: edge.key.to,\n\t\tto: edge.key.from,\n\t}\n\treverse := candidateMutableState(reverseKey)\n\n\tif reverse.known {\n\t\treverse.estimate += amt\n\t\tif reverse.estimate > edge.capacity {\n\t\t\treverse.estimate = edge.capacity\n\t\t}\n\t} else {\n\t\treverse.estimate = amt\n\t}\n\n\treverse.lowerOK += amt\n\tif reverse.lowerOK > edge.capacity {\n\t\treverse.lowerOK = edge.capacity\n\t}\n\tif reverse.upperFail != 0 {\n\t\treverse.upperFail += amt\n\t\tif reverse.upperFail > edge.capacity {\n\t\t\treverse.upperFail = edge.capacity\n\t\t}\n\t}\n\n\treverse.known = true\n\treverse.conf = math.Max(reverse.conf, 0.9)\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tsessionPenalty map[candidateEdgeKey]float64\n\tsessionBlocked map[candidateEdgeKey]bool\n\tattempts uint32\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"payment specification is nil\")\n\t}\n\n\trouter := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tsessionPenalty: make(map[candidateEdgeKey]float64),\n\t\tsessionBlocked: make(map[candidateEdgeKey]bool),\n\t}\n\n\tfor chanID, balance := range localBalances {\n\t\trouter.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node,\n\t\t\tfunc(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.\n\t\t\t\t\t\tFeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\trouter.incomingEdges[key.to] = append(\n\t\t\t\t\trouter.incomingEdges[key.to], edge,\n\t\t\t\t)\n\t\t\t\trouter.edges[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn router, nil\n}\n\nfunc candidatePriorProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.capacity <= 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(amt) / float64(edge.capacity)\n\n\tlowMode := 0.45 * math.Exp(-ratio/0.025)\n\thighMode := 0.50 /\n\t\t(1 + math.Exp((ratio-0.92)/0.04))\n\n\tprobability := 0.025 + lowMode + highMode\n\tif probability > 0.985 {\n\t\tprobability = 0.985\n\t}\n\tif probability < 0.005 {\n\t\tprobability = 0.005\n\t}\n\n\treturn probability\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif r.sessionBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tstate := candidateStateSnapshot(edge.key)\n\tif state.blocked {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif r.localBalances[edge.key.chanID] < amt {\n\t\t\treturn 0\n\t\t}\n\n\t\treturn 1\n\t}\n\n\tif state.upperFail != 0 && amt >= state.upperFail {\n\t\treturn 0\n\t}\n\tif state.lowerOK >= amt {\n\t\treturn 0.995\n\t}\n\n\tprior := candidatePriorProbability(edge, amt)\n\tif !state.known {\n\t\treturn prior\n\t}\n\n\tif state.estimate >= amt {\n\t\tmargin := float64(state.estimate-amt+1) /\n\t\t\tfloat64(edge.capacity+1)\n\t\tprobability := 0.78 + 0.17*state.conf +\n\t\t\t0.04*math.Min(margin, 1)\n\n\t\tif probability > 0.995 {\n\t\t\tprobability = 0.995\n\t\t}\n\n\t\treturn probability\n\t}\n\n\tif state.upperFail != 0 {\n\t\trelative := float64(amt) /\n\t\t\tfloat64(state.upperFail)\n\t\tif relative > 1 {\n\t\t\trelative = 1\n\t\t}\n\n\t\tprobability := 0.03 +\n\t\t\t0.35*math.Pow(1-relative, 3) +\n\t\t\t0.15*prior\n\n\t\tif state.failures > state.successes+1 {\n\t\t\tprobability *= 0.75\n\t\t}\n\t\tif probability < 0.01 {\n\t\t\tprobability = 0.01\n\t\t}\n\n\t\treturn probability\n\t}\n\n\tprobability := 0.35 * prior\n\tif state.successes > state.failures {\n\t\tprobability += 0.15\n\t}\n\tif probability > 0.75 {\n\t\tprobability = 0.75\n\t}\n\tif probability < 0.01 {\n\t\tprobability = 0.01\n\t}\n\n\treturn probability\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk float64\n}\n\ntype candidateQueue []*candidateQueueItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateQueueItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\n\treturn item\n}\n\ntype candidateRouteChoice struct {\n\troute *route.Route\n\tlogRisk float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, 0, errors.New(\"route amount must be positive\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source is payment target\")\n\t}\n\n\tdist := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = 0\n\tqueue := &candidateQueue{}\n\theap.Push(queue, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tamount: deliver,\n\t})\n\n\tvar sourceRisk float64\n\n\tfor queue.Len() != 0 {\n\t\titem := heap.Pop(queue).(*candidateQueueItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceRisk = item.risk\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOver := item.amount\n\t\t\tif !edge.usable(amountOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, amountOver)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amountOver\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tlogRisk := -math.Log(probability) +\n\t\t\t\tr.sessionPenalty[edge.key]\n\t\t\tfeePenalty := 15 * float64(fee) /\n\t\t\t\tmath.Max(float64(deliver), 1)\n\t\t\tedgeScore := logRisk + feePenalty + 0.012\n\t\t\tnewScore := item.score + edgeScore\n\n\t\t\toldScore, exists := dist[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = newScore\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateQueueItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tamount: sending,\n\t\t\t\tscore: newScore,\n\t\t\t\trisk: item.risk + logRisk,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tbuilt, err := r.buildRoute(deliver, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn built, sourceRisk, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tpath := make([]*candidateEdge, 0, 8)\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"cycle in selected route\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"selected route has no hops\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamounts[last] = deliver\n\texpiries[last] = candidateFinalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tnextEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tnextEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := deliver\n\t\toutgoingExpiry := uint32(candidateFinalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tdivisor uint32) lnwire.MilliSatoshi {\n\n\tif divisor <= 1 {\n\t\treturn amt\n\t}\n\n\td := lnwire.MilliSatoshi(divisor)\n\tresult := amt / d\n\tif amt%d != 0 {\n\t\tresult++\n\t}\n\n\treturn result\n}\n\nfunc candidateShardAmounts(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) []lnwire.MilliSatoshi {\n\n\tif partsLeft <= 1 {\n\t\treturn []lnwire.MilliSatoshi{amt}\n\t}\n\n\tlimit := partsLeft\n\tif limit > 24 {\n\t\tlimit = 24\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, 0, limit+1)\n\tvar previous lnwire.MilliSatoshi\n\n\tfor parts := uint32(1); parts <= limit; parts++ {\n\t\tshard := candidateCeilDiv(amt, parts)\n\t\tif shard != previous {\n\t\t\tamounts = append(amounts, shard)\n\t\t\tprevious = shard\n\t\t}\n\t}\n\n\tminimum := candidateCeilDiv(amt, partsLeft)\n\tif minimum != previous {\n\t\tamounts = append(amounts, minimum)\n\t}\n\n\treturn amounts\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(\"remaining amount must be positive\")\n\t}\n\tif r.attempts >= 96 {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum payment parts reached\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tshards := candidateShardAmounts(amt, partsLeft)\n\tminimum := shards[len(shards)-1]\n\n\tthreshold := 0.20\n\tif partsLeft <= 2 {\n\t\tthreshold = 0.08\n\t}\n\n\tvar fallback *candidateRouteChoice\n\tbestUtility := math.Inf(-1)\n\n\tfor _, shard := range shards {\n\t\trt, logRisk, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tprobability := math.Exp(-logRisk)\n\t\tif probability >= threshold {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tprogress := math.Log(\n\t\t\tmath.Max(float64(shard)/float64(minimum), 1),\n\t\t)\n\t\tfee := rt.TotalAmount - shard\n\t\tfeePenalty := 10 * float64(fee) /\n\t\t\tmath.Max(float64(shard), 1)\n\t\tutility := -logRisk + 0.22*progress - feePenalty\n\n\t\tif fallback == nil || utility > bestUtility {\n\t\t\tfallback = &candidateRouteChoice{\n\t\t\t\troute: rt,\n\t\t\t\tlogRisk: logRisk,\n\t\t\t}\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif fallback == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn fallback.route, nil\n}\n\nfunc (r *candidateRouter) routeEdges(\n\trt *route.Route) []*candidateEdge {\n\n\tedges := make([]*candidateEdge, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedges[i] = r.edges[key]\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn edges\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tchannelIndex int) lnwire.MilliSatoshi {\n\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[channelIndex-1].AmtToForward\n}\n\nfunc candidateFailureIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tr.attempts++\n\n\tif rt == nil {\n\t\treturn errors.New(\"reported route is nil\")\n\t}\n\n\tedges := r.routeEdges(rt)\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range edges {\n\t\t\tif edge == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamount := candidateRouteAmount(rt, i)\n\t\t\tcandidateRecordSettlement(edge, amount)\n\t\t\tdelete(r.sessionPenalty, edge.key)\n\t\t}\n\n\t\tif len(rt.Hops) != 0 {\n\t\t\tfirstChan := rt.Hops[0].ChannelID\n\t\t\tspent := rt.TotalAmount\n\t\t\tif r.localBalances[firstChan] > spent {\n\t\t\t\tr.localBalances[firstChan] -= spent\n\t\t\t} else {\n\t\t\t\tr.localBalances[firstChan] = 0\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tif failIndex >= 0 {\n\t\tprefixEnd := failIndex\n\t\tif prefixEnd > len(edges) {\n\t\t\tprefixEnd = len(edges)\n\t\t}\n\n\t\tfor i := 0; i < prefixEnd; i++ {\n\t\t\tedge := edges[i]\n\t\t\tif edge == nil || edge.key.from == r.source {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcandidateRecordProbe(\n\t\t\t\tedge, candidateRouteAmount(rt, i),\n\t\t\t)\n\t\t\tdelete(r.sessionPenalty, edge.key)\n\t\t}\n\t}\n\n\tcode := result.Failure.Code()\n\tif failIndex >= 0 && failIndex < len(edges) {\n\t\tedge := edges[failIndex]\n\t\tif edge == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tswitch code {\n\t\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t\tcandidateRecordFailure(\n\t\t\t\tedge, candidateRouteAmount(rt, failIndex),\n\t\t\t)\n\n\t\t\tretryAmount := candidateRouteAmount(\n\t\t\t\trt, failIndex,\n\t\t\t) * 3 / 8\n\t\t\tif retryAmount > 0 {\n\t\t\t\tstate := candidateStateSnapshot(edge.key)\n\t\t\t\tif state.upperFail > retryAmount {\n\t\t\t\t\tr.sessionPenalty[edge.key] += 0.2\n\t\t\t\t} else {\n\t\t\t\t\tr.sessionPenalty[edge.key] += 0.8\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase lnwire.CodeFeeInsufficient,\n\t\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t\tcandidateBlockEdge(edge)\n\n\t\tdefault:\n\t\t\tr.sessionBlocked[edge.key] = true\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor _, edge := range edges {\n\t\tif edge == nil || edge.key.from == r.source {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.sessionPenalty[edge.key] += 0.45\n\t}\n\n\treturn nil\n}"
},
"role": "best"
}
]
}